Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion docs/03-basic-validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ A much more useful distinction is to categorize the validators according to thei
- `NoneToUnsetValue`: Like `Noneable`, but converts `None` to `UnsetValue`
- `AnythingValidator`: Accepts any input without validation (optionally with type restrictions)
- `RejectValidator`: Rejects any input with a validation error (except for `None` if allowed)
- `DiscardValidator`: Discards any input and returns a predefined value
- `AllowEmptyString`: Wraps another validator but allows the input to be empty string `('')`


Expand Down Expand Up @@ -1150,7 +1151,7 @@ validator.validate({13: 12}) # returns {13: 12}

### RejectValidator

The `RejectValidator` is a special validator rejects any input with a validation error.
The `RejectValidator` is a special validator that rejects any input with a validation error.

This validator can be used for example in dataclasses to define a field that may never be set, or to override an
existing field in a subclassed dataclass that may not be set in this subclass. Keep in mind that in a dataclass
Expand Down Expand Up @@ -1219,6 +1220,42 @@ validator.validate('foo') # raises CustomValidationError with reason='This fiel
```


### DiscardValidator

The `DiscardValidator` is a special validator that discards any input and always returns a predefined value.

This validator accepts any input of any type, similar to the `AnythingValidator`, but ignores the input entirely and
always returns a predefined value instead.

By default, the returned value is `None`. This can be changed using the `output_value` parameter.

This validator should never raise any validation errors, since it doesn't validate anything.

**Examples:**

```python
from validataclass.validators import DiscardValidator, Noneable

# Accepts anything, always returns None
validator = DiscardValidator()
validator.validate(None) # returns None
validator.validate(42) # returns None
validator.validate('') # returns None

# Accepts anything, always returns the string "discarded"
validator = DiscardValidator(output_value="discarded")
validator.validate(None) # returns "discarded"
validator.validate(42) # returns "discarded"
validator.validate('') # returns "discarded"

# Use Noneable to allow None, and replace any other input with the string "discarded"
validator = Noneable(DiscardValidator(output_value="discarded"))
validator.validate(None) # returns None
validator.validate(42) # returns "discarded"
validator.validate('') # returns "discarded"
```


### AllowEmptyString

The `AllowEmptyString` validator wraps another validator and additionally allows empty string `('')` as an input value.
Expand Down
1 change: 1 addition & 0 deletions src/validataclass/validators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .none_to_unset_value import NoneToUnsetValue
from .anything_validator import AnythingValidator
from .reject_validator import RejectValidator
from .discard_validator import DiscardValidator
from .allow_empty_string import AllowEmptyString

# Extended type validators
Expand Down
60 changes: 60 additions & 0 deletions src/validataclass/validators/discard_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
validataclass
Copyright (c) 2022, binary butterfly GmbH and contributors
Use of this source code is governed by an MIT-style license that can be found in the LICENSE file.
"""

from copy import deepcopy
from typing import Any

from .validator import Validator

__all__ = [
'DiscardValidator',
]


class DiscardValidator(Validator):
"""
Special validator that discards any input and always returns a predefined value.

This validator accepts any input of any type, but ignores it entirely and always returns the same predefined value
instead.

By default, the returned value is `None`. This can be changed using the `output_value` parameter.

This validator should never raise any validation errors, since it doesn't validate anything.

Examples:

```
# Accept anything, always returns None
DiscardValidator()

# Accepts anything, always returns the string "discarded"
DiscardValidator(output_value='discarded')
```

See also: `RejectValidator`, `AnythingValidator`

Valid input: Anything
Output: `None` (or output value specified in constructor)
"""

# Value that is returned by the validator
output_value: Any

def __init__(self, *, output_value: Any = None):
"""
Create a DiscardValidator.

Parameters:
output_value: Value of any type that is returned for any input (default: None)
"""
self.output_value = output_value

def validate(self, input_data: Any, **kwargs) -> Any:
"""
Validate input data. Discards any input and always returns None (or the specified `output_value`).
"""
return deepcopy(self.output_value)
47 changes: 47 additions & 0 deletions tests/validators/discard_validator_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
validataclass
Copyright (c) 2022, binary butterfly GmbH and contributors
Use of this source code is governed by an MIT-style license that can be found in the LICENSE file.
"""

import pytest

from validataclass.helpers import UnsetValue
from validataclass.validators import DiscardValidator


class DiscardValidatorTest:
"""
Unit tests for the DiscardValidator.
"""

example_input_data = [None, True, False, 'banana', 42, [], {}]

@staticmethod
@pytest.mark.parametrize('input_data', example_input_data)
def test_discard_and_return_none(input_data):
""" Test that DiscardValidator accepts anything and always returns None by default. """
validator = DiscardValidator()
assert validator.validate(input_data) is None

@staticmethod
@pytest.mark.parametrize('input_data', example_input_data)
@pytest.mark.parametrize('output_value', [None, True, 'discarded value', UnsetValue])
def test_discard_and_return_custom_value(input_data, output_value):
""" Test that DiscardValidator with output_value parameter accepts anything and always returns the specified value. """
validator = DiscardValidator(output_value=output_value)
result = validator.validate(input_data)

assert type(result) is type(output_value)
assert result == output_value

@staticmethod
def test_output_value_is_deepcopied():
""" Test that the given output value is deepcopied (e.g. always return a different empty list with `output_value=[]`). """
validator = DiscardValidator(output_value=[42])
first_list = validator.validate('banana')
second_list = validator.validate('banana')

assert first_list == [42]
assert second_list == [42]
assert first_list is not second_list