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

LeanderCS / flask-inputfilter / #270

17 Apr 2025 09:21PM UTC coverage: 98.094% (-0.5%) from 98.605%
#270

push

coveralls-python

web-flow
Merge pull request #43 from LeanderCS/31

31 | Add fallback for cython

257 of 270 new or added lines in 5 files covered. (95.19%)

1801 of 1836 relevant lines covered (98.09%)

0.98 hits per line

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

98.58
/flask_inputfilter/InputFilter.py
1
from __future__ import annotations
1✔
2

3
import json
1✔
4
import logging
1✔
5
from typing import Any, Dict, List, Optional, Type, TypeVar, Union
1✔
6

7
from flask import Response, g, request
1✔
8

9
from flask_inputfilter.Condition import BaseCondition
1✔
10
from flask_inputfilter.Exception import ValidationError
1✔
11
from flask_inputfilter.Filter import BaseFilter
1✔
12
from flask_inputfilter.Mixin import ExternalApiMixin
1✔
13
from flask_inputfilter.Model import ExternalApiConfig, FieldModel
1✔
14
from flask_inputfilter.Validator import BaseValidator
1✔
15

16
T = TypeVar("T")
1✔
17

18

19
class InputFilter:
1✔
20
    """
21
    Base class for all input filters.
22
    """
23

24
    def __init__(self, methods: Optional[List[str]] = None) -> None:
1✔
25
        self.methods: List[str] = methods or [
1✔
26
            "GET",
27
            "POST",
28
            "PATCH",
29
            "PUT",
30
            "DELETE",
31
        ]
32
        self.fields: Dict[str, FieldModel] = {}
1✔
33
        self.conditions: List[BaseCondition] = []
1✔
34
        self.global_filters: List[BaseFilter] = []
1✔
35
        self.global_validators: List[BaseValidator] = []
1✔
36
        self.data: Dict[str, Any] = {}
1✔
37
        self.validated_data: Dict[str, Any] = {}
1✔
38
        self.errors: Dict[str, str] = {}
1✔
39
        self.model_class: Optional = None
1✔
40

41
    def isValid(self):
1✔
42
        """
43
        Checks if the object's state or its attributes meet certain
44
        conditions to be considered valid. This function is typically used to
45
        ensure that the current state complies with specific requirements or
46
        rules.
47

48
        Returns:
49
            bool: Returns True if the state or attributes of the object fulfill
50
                all required conditions; otherwise, returns False.
51
        """
52
        try:
1✔
53
            self.validateData()
1✔
54

55
        except ValidationError as e:
1✔
56
            self.errors = e.args[0]
1✔
57
            return False
1✔
58

59
        return True
1✔
60

61
    @classmethod
1✔
62
    def validate(
63
        cls,
64
    ):
65
        """
66
        Decorator for validating input data in routes.
67

68
        Args:
69
            cls
70

71
        Returns:
72
            Callable[
73
                [Any],
74
                Callable[
75
                    [Tuple[Any, ...], Dict[str, Any]],
76
                    Union[Response, Tuple[Any, Dict[str, Any]]],
77
                ],
78
            ]
79
        """
80

81
        def decorator(
1✔
82
            f,
83
        ):
84
            """
85
            Decorator function to validate input data for a Flask route.
86

87
            Args:
88
                f (Callable): The Flask route function to be decorated.
89

90
            Returns:
91
                Callable[
92
                    [Any, Any],
93
                    Union[
94
                        Response,
95
                        Tuple[Any, Dict[str, Any]]
96
                    ]
97
                ]: The wrapped function with input validation.
98
            """
99

100
            def wrapper(*args, **kwargs):
1✔
101
                """
102
                Wrapper function to handle input validation and
103
                error handling for the decorated route function.
104

105
                Args:
106
                    *args: Positional arguments for the route function.
107
                    **kwargs: Keyword arguments for the route function.
108

109
                Returns:
110
                    Union[Response, Tuple[Any, Dict[str, Any]]]: The response
111
                        from the route function or an error response.
112
                """
113

114
                input_filter = cls()
1✔
115
                if request.method not in input_filter.methods:
1✔
116
                    return Response(status=405, response="Method Not Allowed")
1✔
117

118
                data = request.json if request.is_json else request.args
1✔
119

120
                try:
1✔
121
                    kwargs = kwargs or {}
1✔
122

123
                    input_filter.data = {**data, **kwargs}
1✔
124

125
                    g.validated_data = input_filter.validateData()
1✔
126

127
                except ValidationError as e:
1✔
128
                    return Response(
1✔
129
                        status=400,
130
                        response=json.dumps(e.args[0]),
131
                        mimetype="application/json",
132
                    )
133

NEW
134
                except Exception:
×
NEW
135
                    logging.getLogger(__name__).exception(
×
136
                        "An unexpected exception occurred while "
137
                        "validating input data.",
138
                    )
NEW
139
                    return Response(status=500)
×
140

141
                return f(*args, **kwargs)
1✔
142

143
            return wrapper
1✔
144

145
        return decorator
1✔
146

147
    def validateData(self, data: Optional[Dict[str, Any]] = None):
1✔
148
        """
149
        Validates input data against defined field rules, including applying
150
        filters, validators, custom logic steps, and fallback mechanisms. The
151
        validation process also ensures the required fields are handled
152
        appropriately and conditions are checked after processing.
153

154
        Args:
155
            data (Dict[str, Any]): A dictionary containing the input data to
156
                be validated where keys represent field names and values
157
                represent the corresponding data.
158

159
        Returns:
160
            Union[Dict[str, Any], Type[T]]: A dictionary containing the
161
                validated data with any modifications, default values,
162
                or processed values as per the defined validation rules.
163

164
        Raises:
165
            Any errors raised during external API calls, validation, or
166
                logical steps execution of the respective fields or conditions
167
                will propagate without explicit handling here.
168
        """
169
        data = data or self.data
1✔
170
        errors = {}
1✔
171

172
        for field_name, field_info in self.fields.items():
1✔
173
            value = data.get(field_name)
1✔
174

175
            required = field_info.required
1✔
176
            default = field_info.default
1✔
177
            fallback = field_info.fallback
1✔
178
            filters = field_info.filters
1✔
179
            validators = field_info.validators
1✔
180
            steps = field_info.steps
1✔
181
            external_api = field_info.external_api
1✔
182
            copy = field_info.copy
1✔
183

184
            try:
1✔
185
                if copy:
1✔
186
                    value = self.validated_data.get(copy)
1✔
187

188
                if external_api:
1✔
189
                    value = ExternalApiMixin().callExternalApi(
1✔
190
                        external_api, fallback, self.validated_data
191
                    )
192

193
                value = self.applyFilters(filters, value)
1✔
194
                value = (
1✔
195
                    self.validateField(validators, fallback, value) or value
196
                )
197
                value = self.applySteps(steps, fallback, value) or value
1✔
198
                value = InputFilter.checkForRequired(
1✔
199
                    field_name, required, default, fallback, value
200
                )
201

202
                self.validated_data[field_name] = value
1✔
203

204
            except ValidationError as e:
1✔
205
                errors[field_name] = str(e)
1✔
206

207
        try:
1✔
208
            self.checkConditions(self.validated_data)
1✔
209
        except ValidationError as e:
1✔
210
            errors["_condition"] = str(e)
1✔
211

212
        if errors:
1✔
213
            raise ValidationError(errors)
1✔
214

215
        if self.model_class is not None:
1✔
216
            return self.serialize()
1✔
217

218
        return self.validated_data
1✔
219

220
    def addCondition(self, condition: BaseCondition):
1✔
221
        """
222
        Add a condition to the input filter.
223

224
        Args:
225
            condition (BaseCondition): The condition to add.
226
        """
227
        self.conditions.append(condition)
1✔
228

229
    def getConditions(self):
1✔
230
        """
231
        Retrieve the list of all registered conditions.
232

233
        This function provides access to the conditions that have been
234
        registered and stored. Each condition in the returned list
235
        is represented as an instance of the BaseCondition type.
236

237
        Returns:
238
            List[BaseCondition]: A list containing all currently registered
239
                instances of BaseCondition.
240
        """
241
        return self.conditions
1✔
242

243
    def checkConditions(self, validated_data: Dict[str, Any]):
1✔
244
        """
245
        Checks if all conditions are met.
246

247
        This method iterates through all registered conditions and checks
248
        if they are satisfied based on the provided validated data. If any
249
        condition is not met, a ValidationError is raised with an appropriate
250
        message indicating which condition failed.
251

252
        Args:
253
            validated_data (Dict[str, Any]):
254
                The validated data to check against the conditions.
255
        """
256
        for condition in self.conditions:
1✔
257
            if not condition.check(validated_data):
1✔
258
                raise ValidationError(
1✔
259
                    f"Condition '{condition.__class__.__name__}' not met."
260
                )
261

262
    def setData(self, data: Dict[str, Any]):
1✔
263
        """
264
        Filters and sets the provided data into the object's internal
265
        storage, ensuring that only the specified fields are considered and
266
        their values are processed through defined filters.
267

268
        Parameters:
269
            data (Dict[str, Any]):
270
                The input dictionary containing key-value pairs where keys
271
                represent field names and values represent the associated
272
                data to be filtered and stored.
273
        """
274
        self.data = {}
1✔
275
        for field_name, field_value in data.items():
1✔
276
            if field_name in self.fields:
1✔
277
                field_value = self.applyFilters(
1✔
278
                    filters=self.fields[field_name].filters,
279
                    value=field_value,
280
                )
281

282
            self.data[field_name] = field_value
1✔
283

284
    def getValue(self, name: str):
1✔
285
        """
286
        This method retrieves a value associated with the provided name. It
287
        searches for the value based on the given identifier and returns the
288
        corresponding result. If no value is found, it typically returns a
289
        default or fallback output. The method aims to provide flexibility in
290
        retrieving data without explicitly specifying the details of the
291
        underlying implementation.
292

293
        Args:
294
            name (str): A string that represents the identifier for which the
295
                 corresponding value is being retrieved. It is used to perform
296
                 the lookup.
297

298
        Returns:
299
            Any: The retrieved value associated with the given name. The
300
                 specific type of this value is dependent on the
301
                 implementation and the data being accessed.
302
        """
303
        return self.validated_data.get(name)
1✔
304

305
    def getValues(self):
1✔
306
        """
307
        Retrieves a dictionary of key-value pairs from the current object.
308
        This method provides access to the internal state or configuration of
309
        the object in a dictionary format, where keys are strings and values
310
        can be of various types depending on the object's design.
311

312
        Returns:
313
            Dict[str, Any]: A dictionary containing string keys and their
314
                            corresponding values of any data type.
315
        """
316
        return self.validated_data
1✔
317

318
    def getRawValue(self, name: str):
1✔
319
        """
320
        Fetches the raw value associated with the provided key.
321

322
        This method is used to retrieve the underlying value linked to the
323
        given key without applying any transformations or validations. It
324
        directly fetches the raw stored value and is typically used in
325
        scenarios where the raw data is needed for processing or debugging
326
        purposes.
327

328
        Args:
329
            name (str): The name of the key whose raw value is to be
330
                retrieved.
331

332
        Returns:
333
            Any: The raw value associated with the provided key.
334
        """
335
        return self.data.get(name) if name in self.data else None
1✔
336

337
    def getRawValues(self):
1✔
338
        """
339
        Retrieves raw values from a given source and returns them as a
340
        dictionary.
341

342
        This method is used to fetch and return unprocessed or raw data in
343
        the form of a dictionary where the keys are strings, representing
344
        the identifiers, and the values are of any data type.
345

346
        Returns:
347
            Dict[str, Any]: A dictionary containing the raw values retrieved.
348
               The keys are strings representing the identifiers, and the
349
               values can be of any type, depending on the source
350
               being accessed.
351
        """
352
        if not self.fields:
1✔
353
            return {}
1✔
354

355
        return {
1✔
356
            field: self.data[field]
357
            for field in self.fields
358
            if field in self.data
359
        }
360

361
    def getUnfilteredData(self):
1✔
362
        """
363
        Fetches unfiltered data from the data source.
364

365
        This method retrieves data without any filtering, processing, or
366
        manipulations applied. It is intended to provide raw data that has
367
        not been altered since being retrieved from its source. The usage
368
        of this method should be limited to scenarios where unprocessed data
369
        is required, as it does not perform any validations or checks.
370

371
        Returns:
372
            Dict[str, Any]: The unfiltered, raw data retrieved from the
373
                 data source. The return type may vary based on the
374
                 specific implementation of the data source.
375
        """
376
        return self.data
1✔
377

378
    def setUnfilteredData(self, data: Dict[str, Any]):
1✔
379
        """
380
        Sets unfiltered data for the current instance. This method assigns a
381
        given dictionary of data to the instance for further processing. It
382
        updates the internal state using the provided data.
383

384
        Parameters:
385
            data (Dict[str, Any]): A dictionary containing the unfiltered
386
                data to be associated with the instance.
387
        """
388
        self.data = data
1✔
389

390
    def hasUnknown(self) -> bool:
1✔
391
        """
392
        Checks whether any values in the current data do not have
393
        corresponding configurations in the defined fields.
394

395
        Returns:
396
            bool: True if there are any unknown fields; False otherwise.
397
        """
398
        if not self.data and self.fields:
1✔
399
            return True
1✔
400

401
        return any(
1✔
402
            field_name not in self.fields.keys()
403
            for field_name in self.data.keys()
404
        )
405

406
    def getErrorMessage(self, field_name: str):
1✔
407
        """
408
        Retrieves and returns a predefined error message.
409

410
        This method is intended to provide a consistent error message
411
        to be used across the application when an error occurs. The
412
        message is predefined and does not accept any parameters.
413
        The exact content of the error message may vary based on
414
        specific implementation, but it is designed to convey meaningful
415
        information about the nature of an error.
416

417
        Args:
418
            field_name (str): The name of the field for which the error
419
                message is being retrieved.
420

421
        Returns:
422
            str: A string representing the predefined error message.
423
        """
424
        return self.errors.get(field_name)
1✔
425

426
    def getErrorMessages(self):
1✔
427
        """
428
        Retrieves all error messages associated with the fields in the
429
        input filter.
430

431
        This method aggregates and returns a dictionary of error messages
432
        where the keys represent field names, and the values are their
433
        respective error messages.
434

435
        Returns:
436
            Dict[str, str]: A dictionary containing field names as keys and
437
                            their corresponding error messages as values.
438
        """
439
        return self.errors
1✔
440

441
    def add(
1✔
442
        self,
443
        name: str,
444
        required: bool = False,
445
        default: Any = None,
446
        fallback: Any = None,
447
        filters: Optional[List[BaseFilter]] = None,
448
        validators: Optional[List[BaseValidator]] = None,
449
        steps: Optional[List[Union[BaseFilter, BaseValidator]]] = None,
450
        external_api: Optional[ExternalApiConfig] = None,
451
        copy: Optional[str] = None,
452
    ):
453
        """
454
        Add the field to the input filter.
455

456
        Args:
457
            name (str): The name of the field.
458

459
            required (Optional[bool]): Whether the field is required.
460

461
            default (Optional[Any]): The default value of the field.
462

463
            fallback (Optional[Any]): The fallback value of the field, if
464
                validations fails or field None, although it is required.
465

466
            filters (Optional[List[BaseFilter]]): The filters to apply to
467
                the field value.
468

469
            validators (Optional[List[BaseValidator]]): The validators to
470
                apply to the field value.
471

472
            steps (Optional[List[Union[BaseFilter, BaseValidator]]]): Allows
473
                to apply multiple filters and validators in a specific order.
474

475
            external_api (Optional[ExternalApiConfig]): Configuration for an
476
                external API call.
477

478
            copy (Optional[str]): The name of the field to copy the value
479
                from.
480
        """
481
        if name in self.fields:
1✔
482
            raise ValueError(f"Field '{name}' already exists.")
1✔
483

484
        self.fields[name] = FieldModel(
1✔
485
            required=required,
486
            default=default,
487
            fallback=fallback,
488
            filters=filters or [],
489
            validators=validators or [],
490
            steps=steps or [],
491
            external_api=external_api,
492
            copy=copy,
493
        )
494

495
    def has(self, field_name: str):
1✔
496
        """
497
        This method checks the existence of a specific field within the
498
        input filter values, identified by its field name. It does not return a
499
        value, serving purely as a validation or existence check mechanism.
500

501
        Args:
502
            field_name (str): The name of the field to check for existence.
503

504
        Returns:
505
            bool: True if the field exists in the input filter,
506
                otherwise False.
507
        """
508
        return field_name in self.fields
1✔
509

510
    def getInput(self, field_name: str):
1✔
511
        """
512
        Represents a method to retrieve a field by its name.
513

514
        This method allows fetching the configuration of a specific field
515
        within the object, using its name as a string. It ensures
516
        compatibility with various field names and provides a generic
517
        return type to accommodate different data types for the fields.
518

519
        Args:
520
            field_name (str): A string representing the name of the field who
521
                        needs to be retrieved.
522

523
        Returns:
524
            Optional[FieldModel]: The field corresponding to the
525
                specified name.
526
        """
527
        return self.fields.get(field_name)
1✔
528

529
    def getInputs(self):
1✔
530
        """
531
        Retrieve the dictionary of input fields associated with the object.
532

533
        Returns:
534
            Dict[str, FieldModel]: Dictionary containing field names as
535
                keys and their corresponding FieldModel instances as values
536
        """
537
        return self.fields
1✔
538

539
    def remove(self, field_name: str):
1✔
540
        """
541
        Removes the specified field from the instance or collection.
542

543
        This method is used to delete a specific field identified by
544
        its name. It ensures the designated field is removed entirely
545
        from the relevant data structure. No value is returned upon
546
        successful execution.
547

548
        Args:
549
            field_name (str): The name of the field to be removed.
550

551
        Returns:
552
            Any: The value of the removed field, if any.
553
        """
554
        return self.fields.pop(field_name, None)
1✔
555

556
    def count(self):
1✔
557
        """
558
        Counts the total number of elements in the collection.
559

560
        This method returns the total count of elements stored within the
561
        underlying data structure, providing a quick way to ascertain the
562
        size or number of entries available.
563

564
        Returns:
565
            int: The total number of elements in the collection.
566
        """
567
        return len(self.fields)
1✔
568

569
    def replace(
1✔
570
        self,
571
        name: str,
572
        required: bool = False,
573
        default: Any = None,
574
        fallback: Any = None,
575
        filters: Optional[List[BaseFilter]] = None,
576
        validators: Optional[List[BaseValidator]] = None,
577
        steps: Optional[List[Union[BaseFilter, BaseValidator]]] = None,
578
        external_api: Optional[ExternalApiConfig] = None,
579
        copy: Optional[str] = None,
580
    ):
581
        """
582
        Replaces a field in the input filter.
583

584
        Args:
585
             name (str): The name of the field.
586

587
            required (Optional[bool]): Whether the field is required.
588

589
            default (Optional[Any]): The default value of the field.
590

591
            fallback (Optional[Any]): The fallback value of the field, if
592
                validations fails or field None, although it is required.
593

594
            filters (Optional[List[BaseFilter]]): The filters to apply to
595
                the field value.
596

597
            validators (Optional[List[BaseValidator]]): The validators to
598
                apply to the field value.
599

600
            steps (Optional[List[Union[BaseFilter, BaseValidator]]]): Allows
601
                to apply multiple filters and validators in a specific order.
602

603
            external_api (Optional[ExternalApiConfig]): Configuration for an
604
                external API call.
605

606
            copy (Optional[str]): The name of the field to copy the value
607
                from.
608
        """
609
        self.fields[name] = FieldModel(
1✔
610
            required=required,
611
            default=default,
612
            fallback=fallback,
613
            filters=filters or [],
614
            validators=validators or [],
615
            steps=steps or [],
616
            external_api=external_api,
617
            copy=copy,
618
        )
619

620
    def applySteps(
1✔
621
        self,
622
        steps: List[Union[BaseFilter, BaseValidator]],
623
        fallback: Any,
624
        value: Any,
625
    ):
626
        """
627
        Apply multiple filters and validators in a specific order.
628

629
        This method processes a given value by sequentially applying a list of
630
        filters and validators. Filters modify the value, while validators
631
        ensure the value meets specific criteria. If a validation error occurs
632
        and a fallback value is provided, the fallback is returned. Otherwise,
633
        the validation error is raised.
634

635
        Args:
636
            steps (List[Union[BaseFilter, BaseValidator]]):
637
                A list of filters and validators to be applied in order.
638
            fallback (Any):
639
                A fallback value to return if validation fails.
640
            value (Any):
641
                The initial value to be processed.
642

643
        Returns:
644
            Any: The processed value after applying all filters and validators.
645
                If a validation error occurs and a fallback is provided, the
646
                fallback value is returned.
647

648
        Raises:
649
            ValidationError: If validation fails and no fallback value is
650
                provided.
651
        """
652
        if value is None:
1✔
653
            return
1✔
654

655
        try:
1✔
656
            for step in steps:
1✔
657
                if isinstance(step, BaseFilter):
1✔
658
                    value = step.apply(value)
1✔
659
                elif isinstance(step, BaseValidator):
1✔
660
                    step.validate(value)
1✔
661
        except ValidationError:
1✔
662
            if fallback is None:
1✔
663
                raise
1✔
664
            return fallback
1✔
665
        return value
1✔
666

667
    @staticmethod
1✔
668
    def checkForRequired(
669
        field_name: str,
670
        required: bool,
671
        default: Any,
672
        fallback: Any,
673
        value: Any,
674
    ):
675
        """
676
        Determine the value of the field, considering the required and
677
        fallback attributes.
678

679
        If the field is not required and no value is provided, the default
680
        value is returned. If the field is required and no value is provided,
681
        the fallback value is returned. If no of the above conditions are met,
682
        a ValidationError is raised.
683

684
        Args:
685
            field_name (str): The name of the field being processed.
686
            required (bool): Indicates whether the field is required.
687
            default (Any): The default value to use if the field is not
688
                provided and not required.
689
            fallback (Any): The fallback value to use if the field is required
690
                but not provided.
691
            value (Any): The current value of the field being processed.
692

693
        Returns:
694
            Any: The determined value of the field after considering required,
695
                default, and fallback attributes.
696

697
        Raises:
698
            ValidationError: If the field is required and no value or fallback
699
                is provided.
700
        """
701
        if value is not None:
1✔
702
            return value
1✔
703

704
        if not required:
1✔
705
            return default
1✔
706

707
        if fallback is not None:
1✔
708
            return fallback
1✔
709

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

712
    def addGlobalFilter(self, filter: BaseFilter):
1✔
713
        """
714
        Add a global filter to be applied to all fields.
715

716
        Args:
717
            filter: The filter to add.
718
        """
719
        self.global_filters.append(filter)
1✔
720

721
    def getGlobalFilters(self):
1✔
722
        """
723
        Retrieve all global filters associated with this InputFilter instance.
724

725
        This method returns a list of BaseFilter instances that have been
726
        added as global filters. These filters are applied universally to
727
        all fields during data processing.
728

729
        Returns:
730
            List[BaseFilter]: A list of global filters.
731
        """
732
        return self.global_filters
1✔
733

734
    def applyFilters(self, filters: List[BaseFilter], value: Any):
1✔
735
        """
736
        Apply filters to the field value.
737

738
        Args:
739
            filters (List[BaseFilter]): A list of filters to apply to the
740
                value.
741
            value (Any): The value to be processed by the filters.
742

743
        Returns:
744
            Any: The processed value after applying all filters.
745
                If the value is None, None is returned.
746
        """
747
        if value is None:
1✔
748
            return
1✔
749

750
        for filter in self.global_filters + filters:
1✔
751
            value = filter.apply(value)
1✔
752

753
        return value
1✔
754

755
    def clear(self):
1✔
756
        """
757
        Resets all fields of the InputFilter instance to
758
        their initial empty state.
759

760
        This method clears the internal storage of fields,
761
        conditions, filters, validators, and data, effectively
762
        resetting the object as if it were newly initialized.
763
        """
764
        self.fields.clear()
1✔
765
        self.conditions.clear()
1✔
766
        self.global_filters.clear()
1✔
767
        self.global_validators.clear()
1✔
768
        self.data.clear()
1✔
769
        self.validated_data.clear()
1✔
770
        self.errors.clear()
1✔
771

772
    def merge(self, other: "InputFilter") -> None:
1✔
773
        """
774
        Merges another InputFilter instance intelligently into the current
775
        instance.
776

777
        - Fields with the same name are merged recursively if possible,
778
            otherwise overwritten.
779
        - Conditions, are combined and duplicated.
780
        - Global filters and validators are merged without duplicates.
781

782
        Args:
783
            other (InputFilter): The InputFilter instance to merge.
784
        """
785
        if not isinstance(other, InputFilter):
1✔
786
            raise TypeError(
1✔
787
                "Can only merge with another InputFilter instance."
788
            )
789

790
        for key, new_field in other.getInputs().items():
1✔
791
            self.fields[key] = new_field
1✔
792

793
        self.conditions += other.conditions
1✔
794

795
        for filter in other.global_filters:
1✔
796
            existing_type_map = {
1✔
797
                type(v): i for i, v in enumerate(self.global_filters)
798
            }
799
            if type(filter) in existing_type_map:
1✔
800
                self.global_filters[existing_type_map[type(filter)]] = filter
1✔
801
            else:
802
                self.global_filters.append(filter)
1✔
803

804
        for validator in other.global_validators:
1✔
805
            existing_type_map = {
1✔
806
                type(v): i for i, v in enumerate(self.global_validators)
807
            }
808
            if type(validator) in existing_type_map:
1✔
809
                self.global_validators[
1✔
810
                    existing_type_map[type(validator)]
811
                ] = validator
812
            else:
813
                self.global_validators.append(validator)
1✔
814

815
    def setModel(self, model_class: Type[T]) -> None:
1✔
816
        """
817
        Set the model class for serialization.
818

819
        Args:
820
            model_class (Type[T]): The class to use for serialization.
821
        """
822
        self.model_class = model_class
1✔
823

824
    def serialize(self) -> Union[Dict[str, Any], T]:
1✔
825
        """
826
        Serialize the validated data. If a model class is set,
827
        returns an instance of that class, otherwise returns the
828
        raw validated data.
829

830
        Returns:
831
            Union[Dict[str, Any], T]: The serialized data.
832
        """
833
        if self.model_class is None:
1✔
834
            return self.validated_data
1✔
835

836
        return self.model_class(**self.validated_data)
1✔
837

838
    def addGlobalValidator(self, validator: BaseValidator) -> None:
1✔
839
        """
840
        Add a global validator to be applied to all fields.
841

842
        Args:
843
            validator (BaseValidator): The validator to add.
844
        """
845
        self.global_validators.append(validator)
1✔
846

847
    def getGlobalValidators(self) -> List[BaseValidator]:
1✔
848
        """
849
        Retrieve all global validators associated with this
850
        InputFilter instance.
851

852
        This method returns a list of BaseValidator instances that have been
853
        added as global validators. These validators are applied universally
854
        to all fields during validation.
855

856
        Returns:
857
            List[BaseValidator]: A list of global validators.
858
        """
859
        return self.global_validators
1✔
860

861
    def validateField(
1✔
862
        self, validators: List[BaseValidator], fallback: Any, value: Any
863
    ) -> Any:
864
        """
865
        Validate the field value.
866

867
        Args:
868
            validators (List[BaseValidator]): A list of validators to apply
869
                to the field value.
870
            fallback (Any): A fallback value to return if validation fails.
871
            value (Any): The value to be validated.
872

873
        Returns:
874
            Any: The validated value if all validators pass. If validation
875
                fails and a fallback is provided, the fallback value is
876
                returned.
877
        """
878
        if value is None:
1✔
879
            return
1✔
880

881
        try:
1✔
882
            for validator in self.global_validators + validators:
1✔
883
                validator.validate(value)
1✔
884
        except ValidationError:
1✔
885
            if fallback is None:
1✔
886
                raise
1✔
887

888
            return fallback
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