Skip to content

Commit

Permalink
fix: make duplicate check case insensitive
Browse files Browse the repository at this point in the history
  • Loading branch information
Ravencentric committed Nov 20, 2024
1 parent 15fce3a commit 4f2bcd7
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 21 deletions.
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pip install stringenum
<Pet.DOG: 'bark'>
```

- `stringenum.DoubleSidedStrEnum` - A subclass of `StrEnum` that supports double-sided lookup, allowing both member values and member names to be used for lookups. It also ensures that each member has a unique value.
- `stringenum.DoubleSidedStrEnum` - A subclass of `StrEnum` that supports double-sided lookup, allowing both member values and member names to be used for lookups.

```py
>>> class Status(DoubleSidedStrEnum):
Expand All @@ -76,8 +76,20 @@ pip install stringenum
>>> Status["denied"]
<Status.REJECTED: 'denied'>
```
Note that `DoubleSidedStrEnum` also does not allow duplicate values.
```
>>> class Fruits(DoubleSidedStrEnum):
... APPLE = "apple"
... BANANA = "banana"
... ORANGE = "apple"
...
Traceback (most recent call last):
...
ValueError: Duplicate values are not allowed in Fruits: <Fruits.ORANGE: 'apple'>
```


- `stringenum.DoubleSidedCaseInsensitiveStrEnum` - A subclass of `StrEnum` that supports case-insenitive double-sided lookup, allowing both member values and member names to be used for lookups. It also ensures that each member has a unique value.
- `stringenum.DoubleSidedCaseInsensitiveStrEnum` - A subclass of `StrEnum` that supports case-insenitive double-sided lookup, allowing both member values and member names to be used for lookups.

```py
>>> class Status(DoubleSidedCaseInsensitiveStrEnum):
Expand All @@ -96,6 +108,17 @@ pip install stringenum
>>> Status["DenieD"]
<Status.REJECTED: 'denied'>
```
Note that `DoubleSidedCaseInsensitiveStrEnum` also does not allow duplicate values.
```
>>> class Fruits(DoubleSidedCaseInsensitiveStrEnum):
... APPLE = "apple"
... BANANA = "banana"
... ORANGE = "apple"
...
Traceback (most recent call last):
...
ValueError: Duplicate values are not allowed in Fruits: <Fruits.ORANGE: 'apple'>
```

## License

Expand Down
28 changes: 10 additions & 18 deletions src/stringenum/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ def _missing_(cls, value: object) -> Self:
raise ValueError(msg)


class _DuplicateFreeStrEnum(StrEnum):
def __init__(self, *args: object) -> None:
cls = self.__class__
if any(self.value.casefold() == member.value.casefold() for member in cls):
msg = f"Duplicate values are not allowed in {self.__class__.__name__}: {self!r}"
raise ValueError(msg)


class _DoubleSidedGetItem(EnumType):
def __getitem__(self, name: str) -> Self: # type: ignore[explicit-override, misc, override]
if not isinstance(name, str):
Expand All @@ -45,21 +53,13 @@ def __getitem__(self, name: str) -> Self: # type: ignore[explicit-override, mis
raise KeyError(name)


class DoubleSidedStrEnum(StrEnum, metaclass=_DoubleSidedGetItem):
class DoubleSidedStrEnum(_DuplicateFreeStrEnum, metaclass=_DoubleSidedGetItem):
"""
A subclass of `StrEnum` that supports double-sided lookup, allowing
both member values and member names to be used for lookups.
It also ensures that each member has a unique value.
"""

def __init__(self, *args: object) -> None:
cls = self.__class__
if any(self.value == member.value for member in cls):
a = self.name
e = cls(self.value).name
msg = f"Aliases not allowed in {self.__class__.__name__}: {a!r} --> {e!r}"
raise ValueError(msg)

@classmethod
def _missing_(cls, value: object) -> Self:
msg = f"'{value}' is not a valid {cls.__name__}"
Expand All @@ -83,21 +83,13 @@ def __getitem__(self, name: str) -> Self: # type: ignore[explicit-override, mis
raise KeyError(name)


class DoubleSidedCaseInsensitiveStrEnum(StrEnum, metaclass=_DoubleSidedCaseInsensitiveGetItem):
class DoubleSidedCaseInsensitiveStrEnum(_DuplicateFreeStrEnum, metaclass=_DoubleSidedCaseInsensitiveGetItem):
"""
A subclass of `StrEnum` that supports case-insenitive double-sided lookup,
allowing both member values and member names to be used for lookups.
It also ensures that each member has a unique value.
"""

def __init__(self, *args: object) -> None:
cls = self.__class__
if any(self.value == member.value for member in cls):
a = self.name
e = cls(self.value).name
msg = f"Aliases not allowed in {self.__class__.__name__}: {a!r} --> {e!r}"
raise ValueError(msg)

@classmethod
def _missing_(cls, value: object) -> Self:
msg = f"'{value}' is not a valid {cls.__name__}"
Expand Down
9 changes: 9 additions & 0 deletions tests/test_double_sided_case_insensitive_strenum.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,12 @@ class InvalidColor(DoubleSidedCaseInsensitiveStrEnum):
RED_COLOR = "Red"
BLUE_SKY = "Blue"
BLUE_DUPLICATE = "Blue"


def test_unique_values_case_insensitively():
with pytest.raises(ValueError):

class InvalidColor(DoubleSidedCaseInsensitiveStrEnum):
RED_COLOR = "Red"
BLUE_SKY = "Blue"
BLUE_DUPLICATE = "blue"
9 changes: 9 additions & 0 deletions tests/test_double_sided_strenum.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,12 @@ class InvalidColor(DoubleSidedStrEnum):
RED_COLOR = "Red"
BLUE_SKY = "Blue"
BLUE_DUPLICATE = "Blue"


def test_unique_values_case_insensitively():
with pytest.raises(ValueError):

class InvalidColor(DoubleSidedStrEnum):
RED_COLOR = "Red"
BLUE_SKY = "Blue"
BLUE_DUPLICATE = "blue"
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4f2bcd7

Please sign in to comment.