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

LeanderCS / flask-inputfilter / #138

26 Mar 2025 04:01PM UTC coverage: 97.046% (-0.6%) from 97.642%
#138

push

coveralls-python

LeanderCS
29 | Add more functions to inputFilter as preparation for future updates and allow user to use InputFilter without decorator

137 of 149 new or added lines in 2 files covered. (91.95%)

1610 of 1659 relevant lines covered (97.05%)

0.97 hits per line

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

94.47
/flask_inputfilter/InputFilter.py
1
import re
1✔
2
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
1✔
3

4
from flask import Response, g, request
1✔
5
from typing_extensions import final
1✔
6

7
from flask_inputfilter.Condition import BaseCondition
1✔
8
from flask_inputfilter.Exception import ValidationError
1✔
9
from flask_inputfilter.Filter import BaseFilter
1✔
10
from flask_inputfilter.Model import ExternalApiConfig, FieldModel
1✔
11
from flask_inputfilter.Validator import BaseValidator
1✔
12

13
API_PLACEHOLDER_PATTERN = re.compile(r"{{(.*?)}}")
1✔
14

15

16
class InputFilter:
1✔
17
    """
18
    Base class for input filters.
19
    """
20

21
    def __init__(self, methods: Optional[List[str]] = None) -> None:
1✔
22
        self.__methods = methods or ["GET", "POST", "PATCH", "PUT", "DELETE"]
1✔
23
        self.__fields: Dict[str, FieldModel] = {}
1✔
24
        self.__conditions: List[BaseCondition] = []
1✔
25
        self.__global_filters: List[BaseFilter] = []
1✔
26
        self.__global_validators: List[BaseValidator] = []
1✔
27
        self.__data: Dict[str, Any] = {}
1✔
28
        self.__validated_data: Dict[str, Any] = {}
1✔
29
        self.__error_message: str = ""
1✔
30

31
    @final
1✔
32
    def add(
1✔
33
        self,
34
        name: str,
35
        required: bool = False,
36
        default: Any = None,
37
        fallback: Any = None,
38
        filters: Optional[List[BaseFilter]] = None,
39
        validators: Optional[List[BaseValidator]] = None,
40
        steps: Optional[List[Union[BaseFilter, BaseValidator]]] = None,
41
        external_api: Optional[ExternalApiConfig] = None,
42
        copy: Optional[str] = None,
43
    ) -> None:
44
        """
45
        Add the field to the input filter.
46

47
        Args:
48
            name: The name of the field.
49
            required: Whether the field is required.
50
            default: The default value of the field.
51
            fallback: The fallback value of the field, if validations fails
52
                or field None, although it is required .
53
            filters: The filters to apply to the field value.
54
            validators: The validators to apply to the field value.
55
            steps: Allows to apply multiple filters and validators
56
                in a specific order.
57
            external_api: Configuration for an external API call.
58
            copy: The name of the field to copy the value from.
59
        """
60
        self.__fields[name] = FieldModel(
1✔
61
            required=required,
62
            default=default,
63
            fallback=fallback,
64
            filters=filters or [],
65
            validators=validators or [],
66
            steps=steps or [],
67
            external_api=external_api,
68
            copy=copy,
69
        )
70

71
    @final
1✔
72
    def addCondition(self, condition: BaseCondition) -> None:
1✔
73
        """
74
        Add a condition to the input filter.
75

76
        Args:
77
            condition: The condition to add.
78
        """
79
        self.__conditions.append(condition)
1✔
80

81
    @final
1✔
82
    def addGlobalFilter(self, filter: BaseFilter) -> None:
1✔
83
        """
84
        Add a global filter to be applied to all fields.
85

86
        Args:
87
            filter: The filter to add.
88
        """
89
        self.__global_filters.append(filter)
1✔
90

91
    @final
1✔
92
    def addGlobalValidator(self, validator: BaseValidator) -> None:
1✔
93
        """
94
        Add a global validator to be applied to all fields.
95

96
        Args:
97
            validator: The validator to add.
98
        """
99
        self.__global_validators.append(validator)
1✔
100

101
    @final
1✔
102
    def has(self, field_name: str) -> bool:
1✔
103
        """
104
        This method checks the existence of a specific field within the input filter values,
105
        identified by its field name. It does not return a value, serving purely as a validation
106
        or existence check mechanism.
107

108
        Args:
109
            field_name (str): The name of the field to check for existence.
110

111
        Returns:
112
            bool: True if the field exists in the input filter, otherwise False.
113
        """
114
        return field_name in self.__fields
1✔
115

116
    @final
1✔
117
    def getInput(self, field_name: str) -> FieldModel:
1✔
118
        """
119
        Represents a method to retrieve the value of a field by its name.
120

121
        This method allows fetching the value of a specific field within the
122
        object, using its name as a string. It ensures compatibility with
123
        various field names and provides a generic return type to accommodate
124
        different data types for the fields.
125

126
        Args:
127
            field_name: A string representing the name of the field whose value
128
                        needs to be retrieved.
129

130
        Returns:
131
            Any: The value of the field corresponding to the specified name.
132
        """
133
        return self.__fields.get(field_name)
1✔
134

135
    @final
1✔
136
    def getInputs(self) -> Dict[str, FieldModel]:
1✔
137
        """
138
        Retrieve the dictionary of input fields associated with the object.
139

140
        Returns:
141
            Dict[str, FieldModel]: Dictionary containing field names as
142
                keys and their corresponding FieldModel instances as values
143
        """
144
        return self.__fields
1✔
145

146
    @final
1✔
147
    def remove(self, field_name: str) -> Any:
1✔
148
        """
149
        Removes the specified field from the instance or collection.
150

151
        This method is used to delete a specific field identified by
152
        its name. It ensures the designated field is removed entirely
153
        from the relevant data structure. No value is returned upon
154
        successful execution.
155

156
        Args:
157
            field_name: The name of the field to be removed.
158

159
        Returns:
160
            Any: The value of the removed field, if any.
161
        """
162
        return self.__fields.pop(field_name, None)
1✔
163

164
    @final
1✔
165
    def count(self) -> int:
1✔
166
        """
167
        Counts the total number of elements in the collection.
168

169
        This method returns the total count of elements stored within the underlying
170
        data structure, providing a quick way to ascertain the size or number of
171
        entries available.
172

173
        Returns:
174
            int: The total number of elements in the collection.
175
        """
176
        return len(self.__fields)
1✔
177

178
    @final
1✔
179
    def replace(
1✔
180
        self,
181
        name: str,
182
        required: bool = False,
183
        default: Any = None,
184
        fallback: Any = None,
185
        filters: Optional[List[BaseFilter]] = None,
186
        validators: Optional[List[BaseValidator]] = None,
187
        steps: Optional[List[Union[BaseFilter, BaseValidator]]] = None,
188
        external_api: Optional[ExternalApiConfig] = None,
189
        copy: Optional[str] = None,
190
    ) -> None:
191
        """
192
        Replaces a field in the input filter.
193

194
        Args:
195
            name: The name of the field.
196
            required: Whether the field is required.
197
            default: The default value of the field.
198
            fallback: The fallback value of the field, if validations fails
199
                or field None, although it is required .
200
            filters: The filters to apply to the field value.
201
            validators: The validators to apply to the field value.
202
            steps: Allows to apply multiple filters and validators
203
                in a specific order.
204
            external_api: Configuration for an external API call.
205
            copy: The name of the field to copy the value from.
206
        """
207
        self.__fields[name] = FieldModel(
1✔
208
            required=required,
209
            default=default,
210
            fallback=fallback,
211
            filters=filters or [],
212
            validators=validators or [],
213
            steps=steps or [],
214
            external_api=external_api,
215
            copy=copy,
216
        )
217

218
    @final
1✔
219
    def setData(self, data: Dict[str, Any]) -> None:
1✔
220
        """
221
        Filters and sets the provided data into the object's internal storage, ensuring
222
        that only the specified fields are considered and their values are processed
223
        through defined filters.
224

225
        Parameters:
226
            data:
227
                The input dictionary containing key-value pairs where keys represent field
228
                names and values represent the associated data to be filtered and stored.
229
        """
230
        filtered_data = {}
1✔
231
        for field_name, field_value in data.items():
1✔
232
            if field_name in self.__fields:
1✔
233
                filtered_data[field_name] = self.__applyFilters(
1✔
234
                    filters=self.__fields[field_name].filters,
235
                    value=field_value,
236
                )
237
            else:
238
                filtered_data[field_name] = field_value
1✔
239

240
        self.__data = filtered_data
1✔
241

242
    @final
1✔
243
    def getErrorMessage(self) -> str:
1✔
244
        """
245
        Retrieves and returns a predefined error message.
246

247
        This method is intended to provide a consistent error message
248
        to be used across the application when an error occurs. The
249
        message is predefined and does not accept any parameters.
250
        The exact content of the error message may vary based on
251
        specific implementation, but it is designed to convey meaningful
252
        information about the nature of an error.
253

254
        Returns:
255
            str: A string representing the predefined error message.
256
        """
257
        return self.__error_message
1✔
258

259
    @final
1✔
260
    def getValue(self, name: str) -> Any:
1✔
261
        """
262
        This method retrieves a value associated with the provided name. It searches
263
        for the value based on the given identifier and returns the corresponding
264
        result. If no value is found, it typically returns a default or fallback
265
        output. The method aims to provide flexibility in retrieving data without
266
        explicitly specifying the details of the underlying implementation.
267

268
        Args:
269
            name: A string that represents the identifier for which the corresponding
270
                  value is being retrieved. It is used to perform the lookup.
271

272
        Returns:
273
            Any: The retrieved value associated with the given name. The specific
274
                 type of this value is dependent on the implementation and the data
275
                 being accessed.
276
        """
277
        return self.__validated_data.get(name)
1✔
278

279
    @final
1✔
280
    def getValues(self) -> Dict[str, Any]:
1✔
281
        """
282
        Retrieves a dictionary of key-value pairs from the current object. This
283
        method provides access to the internal state or configuration of the
284
        object in a dictionary format, where keys are strings and values can
285
        be of various types depending on the object’s design.
286

287
        Returns:
288
            Dict[str, Any]: A dictionary containing string keys and their
289
                            corresponding values of any data type.
290
        """
291
        return self.__validated_data
1✔
292

293
    @final
1✔
294
    def getRawValue(self, name: str) -> Any:
1✔
295
        """
296
        Fetches the raw value associated with the provided key.
297

298
        This method is used to retrieve the underlying value linked to the given
299
        key without applying any transformations or validations. It directly fetches
300
        the raw stored value and is typically used in scenarios where the raw data
301
        is needed for processing or debugging purposes.
302

303
        Args:
304
            name: The name of the key whose raw value is to be retrieved.
305

306
        Returns:
307
            Any: The raw value associated with the provided key.
308
        """
309
        return self.__data.get(name) if name in self.__data else None
1✔
310

311
    @final
1✔
312
    def getRawValues(self) -> Dict[str, Any]:
1✔
313
        """
314
        Retrieves raw values from a given source and returns them as a dictionary.
315

316
        This method is used to fetch and return unprocessed or raw data in the form
317
        of a dictionary where the keys are strings, representing the identifiers, and
318
        the values are of any data type.
319

320
        Returns:
321
            Dict[str, Any]: A dictionary containing the raw values retrieved.
322
               The keys are strings representing the identifiers, and the
323
               values can be of any type, depending on the source
324
               being accessed.
325
        """
326
        if not self.__fields:
1✔
NEW
327
            return {}
×
328

329
        return {
1✔
330
            field: self.__data[field]
331
            for field in self.__fields
332
            if field in self.__data
333
        }
334

335
    @final
1✔
336
    def getUnfilteredData(self) -> Dict[str, Any]:
1✔
337
        """
338
        Fetches unfiltered data from the data source.
339

340
        This method retrieves data without any filtering, processing, or
341
        manipulations applied. It is intended to provide raw data that has
342
        not been altered since being retrieved from its source. The usage
343
        of this method should be limited to scenarios where unprocessed data
344
        is required, as it does not perform any validations or checks.
345

346
        Returns:
347
            Dict[str, Any]: The unfiltered, raw data retrieved from the data source. The
348
                 return type may vary based on the specific implementation of
349
                 the data source.
350
        """
351
        return self.__data
1✔
352

353
    @final
1✔
354
    def setUnfilteredData(self, data: Dict[str, Any]) -> None:
1✔
355
        """
356
        Sets unfiltered data for the current instance. This method assigns a
357
        given dictionary of data to the instance for further processing.
358
        It updates the internal state using the provided data.
359

360
        Parameters:
361
            data: A dictionary containing the unfiltered
362
                data to be associated with the instance.
363
        """
364
        self.__data = data
1✔
365

366
    @final
1✔
367
    def hasUnknown(self) -> bool:
1✔
368
        """
369
        Checks whether any values in the current data do not
370
        have corresponding configurations in the defined fields.
371

372
        Returns:
373
            bool: True if there are any unknown fields; False otherwise.
374
        """
375
        if not self.__data and self.__fields:
1✔
376
            return True
1✔
377
        return any(
1✔
378
            field_name not in self.__fields.keys()
379
            for field_name in self.__data.keys()
380
        )
381

382
    @final
1✔
383
    def merge(self, other: "InputFilter") -> None:
1✔
384
        """
385
        Merges another InputFilter instance intelligently into the current instance.
386

387
        - Fields with the same name are merged recursively if possible, otherwise overwritten.
388
        - Conditions are combined and deduplicated.
389
        - Global filters and validators are merged without duplicates.
390

391
        Args:
392
            other (InputFilter): The InputFilter instance to merge.
393
        """
394
        if not isinstance(other, InputFilter):
1✔
NEW
395
            raise TypeError(
×
396
                "Can only merge with another InputFilter instance."
397
            )
398

399
        for key, new_field in other.getInputs().items():
1✔
400
            if key not in self.__fields:
1✔
401
                self.__fields[key] = new_field
1✔
402
                continue
1✔
403

NEW
404
            existing_field = self.__fields[key]
×
NEW
405
            if not isinstance(existing_field, InputFilter) or not isinstance(
×
406
                new_field, InputFilter
407
            ):
NEW
408
                self.__fields[key] = new_field
×
409

NEW
410
            existing_field.merge(new_field)
×
411

412
        self.__conditions = list(set(self.__conditions + other.__conditions))
1✔
413

414
        existing_global_filters = set(self.__global_filters)
1✔
415
        for filter in other.__global_filters:
1✔
NEW
416
            if filter not in existing_global_filters:
×
NEW
417
                self.__global_filters.append(filter)
×
NEW
418
                existing_global_filters.add(filter)
×
419

420
        existing_global_validators = set(self.__global_validators)
1✔
421
        for validator in other.__global_validators:
1✔
NEW
422
            if validator not in existing_global_validators:
×
NEW
423
                self.__global_validators.append(validator)
×
NEW
424
                existing_global_validators.add(validator)
×
425

426
    @final
1✔
427
    def isValid(self) -> bool:
1✔
428
        """
429
        Checks if the object's state or its attributes meet certain conditions
430
        to be considered valid. This function is typically used to ensure that
431
        the current state complies with specific requirements or rules.
432

433
        Returns:
434
            bool: Returns True if the state or attributes of the object fulfill
435
                all required conditions; otherwise, returns False.
436
        """
437
        try:
1✔
438
            self.validateData(self.__data)
1✔
439

440
        except (ValidationError, Exception) as e:
1✔
441
            self.__error_message = str(e)
1✔
442
            return False
1✔
443

444
        return True
1✔
445

446
    @final
1✔
447
    def validateData(
1✔
448
        self, data: Optional[Dict[str, Any]] = None
449
    ) -> Dict[str, Any]:
450
        """
451
        Validates input data against defined field rules, including applying filters, validators,
452
        custom logic steps, and fallback mechanisms. The validation process also ensures the
453
        required fields are handled appropriately and conditions are checked after processing.
454

455
        Args:
456
            data (Dict[str, Any]): A dictionary containing the input data to be validated
457
            where keys represent field names and values represent the corresponding data.
458

459
        Returns:
460
            Dict[str, Any]: A dictionary containing the validated data with any modifications,
461
            default values, or processed values as per the defined validation rules.
462

463
        Raises:
464
            Any errors raised during external API calls, validation, or logical steps execution
465
            of the respective fields or conditions will propagate without explicit handling here.
466
        """
467
        validated_data = self.__validated_data
1✔
468
        data = data or self.__data
1✔
469

470
        for field_name, field_info in self.__fields.items():
1✔
471
            value = data.get(field_name)
1✔
472

473
            required = field_info.required
1✔
474
            default = field_info.default
1✔
475
            fallback = field_info.fallback
1✔
476
            filters = field_info.filters
1✔
477
            validators = field_info.validators
1✔
478
            steps = field_info.steps
1✔
479
            external_api = field_info.external_api
1✔
480
            copy = field_info.copy
1✔
481

482
            if copy:
1✔
483
                value = validated_data.get(copy)
1✔
484

485
            if external_api:
1✔
486
                value = self.__callExternalApi(
1✔
487
                    external_api, fallback, validated_data
488
                )
489

490
            value = self.__applyFilters(filters, value)
1✔
491

492
            value = self.__validateField(validators, fallback, value) or value
1✔
493

494
            value = self.__applySteps(steps, fallback, value) or value
1✔
495

496
            value = self.__checkForRequired(
1✔
497
                field_name, required, default, fallback, value
498
            )
499

500
            validated_data[field_name] = value
1✔
501

502
        self.__checkConditions(validated_data)
1✔
503

504
        self.__validated_data = validated_data
1✔
505

506
        return validated_data
1✔
507

508
    @classmethod
1✔
509
    @final
1✔
510
    def validate(
1✔
511
        cls,
512
    ) -> Callable[
513
        [Any],
514
        Callable[
515
            [Tuple[Any, ...], Dict[str, Any]],
516
            Union[Response, Tuple[Any, Dict[str, Any]]],
517
        ],
518
    ]:
519
        """
520
        Decorator for validating input data in routes.
521
        """
522

523
        def decorator(
1✔
524
            f,
525
        ) -> Callable[
526
            [Tuple[Any, ...], Dict[str, Any]],
527
            Union[Response, Tuple[Any, Dict[str, Any]]],
528
        ]:
529
            def wrapper(
1✔
530
                *args, **kwargs
531
            ) -> Union[Response, Tuple[Any, Dict[str, Any]]]:
532
                input_filter = cls()
1✔
533
                if request.method not in input_filter.__methods:
1✔
534
                    return Response(status=405, response="Method Not Allowed")
1✔
535

536
                data = request.json if request.is_json else request.args
1✔
537

538
                try:
1✔
539
                    kwargs = kwargs or {}
1✔
540

541
                    input_filter.__data = {**data, **kwargs}
1✔
542

543
                    g.validated_data = input_filter.validateData()
1✔
544

545
                except ValidationError as e:
1✔
546
                    return Response(status=400, response=str(e))
1✔
547

548
                return f(*args, **kwargs)
1✔
549

550
            return wrapper
1✔
551

552
        return decorator
1✔
553

554
    def __applyFilters(self, filters: List[BaseFilter], value: Any) -> Any:
1✔
555
        """
556
        Apply filters to the field value.
557
        """
558
        if value is None:
1✔
559
            return value
1✔
560

561
        for filter_ in self.__global_filters + filters:
1✔
562
            value = filter_.apply(value)
1✔
563

564
        return value
1✔
565

566
    def __validateField(
1✔
567
        self, validators: List[BaseValidator], fallback: Any, value: Any
568
    ) -> None:
569
        """
570
        Validate the field value.
571
        """
572
        if value is None:
1✔
573
            return
1✔
574

575
        try:
1✔
576
            for validator in self.__global_validators + validators:
1✔
577
                validator.validate(value)
1✔
578
        except ValidationError:
1✔
579
            if fallback is None:
1✔
580
                raise
1✔
581

582
            return fallback
1✔
583

584
    @staticmethod
1✔
585
    def __applySteps(
1✔
586
        steps: List[Union[BaseFilter, BaseValidator]],
587
        fallback: Any,
588
        value: Any,
589
    ) -> Any:
590
        """
591
        Apply multiple filters and validators in a specific order.
592
        """
593
        if value is None:
1✔
594
            return
1✔
595

596
        try:
1✔
597
            for step in steps:
1✔
598
                if isinstance(step, BaseFilter):
1✔
599
                    value = step.apply(value)
1✔
600
                elif isinstance(step, BaseValidator):
1✔
601
                    step.validate(value)
1✔
602
        except ValidationError:
1✔
603
            if fallback is None:
1✔
604
                raise
1✔
605
            return fallback
1✔
606
        return value
1✔
607

608
    def __callExternalApi(
1✔
609
        self, config: ExternalApiConfig, fallback: Any, validated_data: dict
610
    ) -> Optional[Any]:
611
        """
612
        Makes a call to an external API using provided configuration and
613
        returns the response.
614

615
        Summary:
616
        The function constructs a request based on the given API
617
        configuration and validated data, including headers, parameters,
618
        and other request settings. It utilizes the `requests` library
619
        to send the API call and processes the response. If a fallback
620
        value is supplied, it is returned in case of any failure during
621
        the API call. If no fallback is provided, a validation error is
622
        raised.
623

624
        Parameters:
625
            config:
626
                An object containing the configuration details for the
627
                external API call, such as URL, headers, method, and API key.
628
            fallback:
629
                The value to be returned in case the external API call fails.
630
            validated_data:
631
                The dictionary containing data used to replace placeholders
632
                in the URL and parameters of the API request.
633

634
        Returns:
635
            Optional[Any]:
636
                The JSON-decoded response from the API, or the fallback
637
                value if the call fails and a fallback is provided.
638

639
        Raises:
640
            ValidationError
641
                Raised if the external API call does not succeed and no
642
                fallback value is provided.
643
        """
644
        import requests
1✔
645

646
        requestData = {
1✔
647
            "headers": {},
648
            "params": {},
649
        }
650

651
        if config.api_key:
1✔
652
            requestData["headers"]["Authorization"] = (
1✔
653
                f"Bearer " f"{config.api_key}"
654
            )
655

656
        if config.headers:
1✔
657
            requestData["headers"].update(config.headers)
1✔
658

659
        if config.params:
1✔
660
            requestData["params"] = self.__replacePlaceholdersInParams(
1✔
661
                config.params, validated_data
662
            )
663

664
        requestData["url"] = self.__replacePlaceholders(
1✔
665
            config.url, validated_data
666
        )
667
        requestData["method"] = config.method
1✔
668

669
        try:
1✔
670
            response = requests.request(**requestData)
1✔
671

672
            if response.status_code != 200:
1✔
673
                raise ValidationError(
1✔
674
                    f"External API call failed with "
675
                    f"status code {response.status_code}"
676
                )
677

678
            result = response.json()
1✔
679

680
            data_key = config.data_key
1✔
681
            if data_key:
1✔
682
                return result.get(data_key)
1✔
683

684
            return result
×
685
        except Exception as e:
1✔
686
            if fallback is None:
1✔
687
                self.__error_message = str(e)
1✔
688

689
                raise ValidationError(
1✔
690
                    f"External API call failed for field "
691
                    f"'{config.data_key}'."
692
                )
693

694
            return fallback
1✔
695

696
    @staticmethod
1✔
697
    def __replacePlaceholders(value: str, validated_data: dict) -> str:
1✔
698
        """
699
        Replace all placeholders, marked with '{{ }}' in value
700
        with the corresponding values from validated_data.
701
        """
702
        return API_PLACEHOLDER_PATTERN.sub(
1✔
703
            lambda match: str(validated_data.get(match.group(1))),
704
            value,
705
        )
706

707
    def __replacePlaceholdersInParams(
1✔
708
        self, params: dict, validated_data: dict
709
    ) -> dict:
710
        """
711
        Replace all placeholders in params with the
712
        corresponding values from validated_data.
713
        """
714
        return {
1✔
715
            key: self.__replacePlaceholders(value, validated_data)
716
            if isinstance(value, str)
717
            else value
718
            for key, value in params.items()
719
        }
720

721
    @staticmethod
1✔
722
    def __checkForRequired(
1✔
723
        field_name: str,
724
        required: bool,
725
        default: Any,
726
        fallback: Any,
727
        value: Any,
728
    ) -> Any:
729
        """
730
        Determine the value of the field, considering the required and
731
        fallback attributes.
732

733
        If the field is not required and no value is provided, the default
734
        value is returned.
735
        If the field is required and no value is provided, the fallback
736
        value is returned.
737
        If no of the above conditions are met, a ValidationError is raised.
738
        """
739
        if value is not None:
1✔
740
            return value
1✔
741

742
        if not required:
1✔
743
            return default
1✔
744

745
        if fallback is not None:
1✔
746
            return fallback
1✔
747

748
        raise ValidationError(f"Field '{field_name}' is required.")
1✔
749

750
    def __checkConditions(self, validated_data: dict) -> None:
1✔
751
        for condition in self.__conditions:
1✔
752
            if not condition.check(validated_data):
1✔
753
                raise ValidationError(f"Condition '{condition}' not met.")
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc