Skip to content

Commit

Permalink
Support lists as field value choices
Browse files Browse the repository at this point in the history
  • Loading branch information
subhashb committed Jun 5, 2024
1 parent 0a7798c commit 0a04bbb
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 48 deletions.
28 changes: 16 additions & 12 deletions src/protean/fields/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,6 @@ def __init__(

# Set the choices for this field
self.choices = choices
if self.choices:
self.choice_dict = {}
for _, member in self.choices.__members__.items():
if isinstance(member.value, (tuple, list)):
self.choice_dict[member.value[0]] = member.value[1]
else:
self.choice_dict[member.value] = member.value

self._validators = validators

Expand Down Expand Up @@ -251,12 +244,23 @@ def _load(self, value: Any):

# If choices exist then validate that value is be one of the choices
if self.choices:
value_list = value
if not isinstance(value, (list, tuple)):
value_list = [value]
# Check if self.choices is an Enum
if type(self.choices) not in [list, tuple] and issubclass(
self.choices, enum.Enum
):
choices = [item.value for item in self.choices]

# Check if value is an Enum instance
if isinstance(value, self.choices):
value = value.value
else:
choices = self.choices

value_list = [value] if not isinstance(value, (list, tuple)) else value

for v in value_list:
if v not in self.choice_dict:
self.fail("invalid_choice", value=v, choices=list(self.choice_dict))
if v not in choices:
self.fail("invalid_choice", value=v, choices=choices)

# Cast and Validate the value for this Field
value = self._cast_to_type(value)
Expand Down
6 changes: 0 additions & 6 deletions src/protean/fields/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ def __init__(self, max_length=255, min_length=None, sanitize=True, **kwargs):

def _cast_to_type(self, value):
"""Convert the value to its string representation"""
if value is None:
return value

value = value if isinstance(value, str) else str(value)

return bleach.clean(value) if self.sanitize else value
Expand Down Expand Up @@ -77,9 +74,6 @@ def __init__(self, sanitize=True, **kwargs):

def _cast_to_type(self, value):
"""Convert the value to its string representation"""
if value is None:
return value

value = value if isinstance(value, str) else str(value)

return bleach.clean(value) if self.sanitize else value
Expand Down
27 changes: 0 additions & 27 deletions tests/field/test_associations.py

This file was deleted.

57 changes: 57 additions & 0 deletions tests/field/test_choices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import pytest

from enum import Enum

from protean.exceptions import ValidationError
from protean.fields import String


def test_choices_as_enum():
"""Test choices validations for the string field"""

class StatusChoices(Enum):
"""Set of choices for the status"""

PENDING = "Pending"
SUCCESS = "Success"
ERROR = "Error"

status = String(max_length=10, choices=StatusChoices)
assert status is not None

# Test loading a value
assert status._load("Pending") == "Pending"
# Test loading an Enum
assert status._load(StatusChoices.PENDING) == "Pending"

# Test invalid value
with pytest.raises(ValidationError) as e_info:
status._load("Failure")

assert e_info.value.messages == {
"unlinked": [
"Value `'Failure'` is not a valid choice. "
"Must be among ['Pending', 'Success', 'Error']"
]
}


def test_choices_as_list():
"""Test choices validations for the string field"""

status = String(max_length=10, choices=["Pending", "Success", "Error"])
assert status is not None

# Test loading a value
assert status._load("Pending") == "Pending"

# Test invalid value
with pytest.raises(ValidationError) as e_info:
status._load("Failure")

assert e_info.value.messages == {
"unlinked": [
"Value `'Failure'` is not a valid choice. "
"Must be among ['Pending', 'Success', 'Error']"
]
}
10 changes: 7 additions & 3 deletions tests/field/test_field_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ def test_init(self):
name = String(max_length=10)
assert name is not None

def test_returns_none_as_it_is(self):
name = String(max_length=10)
assert name._load(None) is None

def test_type_validation(self):
"""Test type checking validation for the Field"""
name = String(max_length=10)
Expand Down Expand Up @@ -117,9 +121,9 @@ def test_choice(self):
class StatusChoices(enum.Enum):
"""Set of choices for the status"""

PENDING = (0, "Pending")
SUCCESS = (1, "Success")
ERROR = (2, "Error")
PENDING = 0
SUCCESS = 1
ERROR = 2

status = Integer(choices=StatusChoices)
assert status is not None
Expand Down

0 comments on commit 0a04bbb

Please sign in to comment.