Skip to content

Commit

Permalink
tidy enum utils
Browse files Browse the repository at this point in the history
  • Loading branch information
aaxelb committed Feb 8, 2024
1 parent 224d596 commit b442bce
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 73 deletions.
4 changes: 2 additions & 2 deletions addon_service/authorized_storage_account/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
)
from rest_framework_json_api.utils import get_resource_type_from_model

from addon_service.capability.serializers import StorageCapabilityField
from addon_service.capability.serializers import StorageCapabilityListField
from addon_service.models import (
AuthorizedStorageAccount,
ConfiguredStorageAddon,
Expand Down Expand Up @@ -44,7 +44,7 @@ def __init__(self, *args, **kwargs):
url = serializers.HyperlinkedIdentityField(
view_name=f"{RESOURCE_NAME}-detail", required=False
)
authorized_capabilities = StorageCapabilityField()
authorized_capabilities = StorageCapabilityListField()
account_owner = AccountOwnerField(
many=False,
queryset=UserReference.objects.all(),
Expand Down
29 changes: 2 additions & 27 deletions addon_service/capability/models.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,10 @@
import enum
from typing import ClassVar

from addon_service.common.enums import IntEnumForEnum
from addon_toolkit.categories.storage import StorageCapability


__all__ = ("IntStorageCapability",)


class _IntEnumForEnum(enum.IntEnum):
__base_enum: ClassVar[type[enum.Enum]]

def __init_subclass__(cls, /, base_enum: type[enum.Enum], **kwargs):
super().__init_subclass__(**kwargs)
cls.__base_enum = base_enum
# ensure enums have same names
_base_names = {_item.name for _item in base_enum}
_int_names = {_item.name for _item in cls}
assert _base_names == _int_names

@classmethod
def to_int(cls, base_enum_member):
return cls[base_enum_member.name]

@classmethod
def as_django_choices(cls):
return [(int(_item), _item.name) for _item in cls]

def to_base_enum(self) -> enum.Enum:
return self.__base_enum[self.name]


class IntStorageCapability(_IntEnumForEnum, base_enum=StorageCapability):
class IntStorageCapability(IntEnumForEnum, base_enum=StorageCapability):
ACCESS = 1
UPDATE = 2
51 changes: 9 additions & 42 deletions addon_service/capability/serializers.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,11 @@
import enum
from addon_service.capability.models import IntStorageCapability
from addon_service.common.enums.serializers import DualEnumsListField
from addon_toolkit.categories.storage import StorageCapability

from rest_framework_json_api import serializers

from addon_service.capability.models import (
IntStorageCapability,
StorageCapability,
)


class EnumsMultipleChoiceField(serializers.MultipleChoiceField):
__internal_enum: type[enum.Enum]
__external_enum: type[enum.Enum]

def __init__(self, /, internal_enum, external_enum, **kwargs):
_choices = {_external_member.value for _external_member in external_enum}
super().__init__(**kwargs, choices=_choices)
self.__internal_enum = internal_enum
self.__external_enum = external_enum

def to_internal_value(self, data):
_names = super().to_internal_value(data)
return [self._to_internal_enum_member(_name) for _name in _names]

def to_representation(self, value):
_member_list = super().to_representation(value)
return {self._to_external_enum_value(_member) for _member in _member_list}

def _to_internal_enum_member(self, external_value):
_external_member = self.__external_enum(external_value)
return self.__internal_enum[_external_member.name]

def _to_external_enum_value(self, internal_value):
_internal_member = self.__internal_enum(internal_value)
_external_member = self.__external_enum[_internal_member.name]
return _external_member.value


def StorageCapabilityField(**kwargs):
return EnumsMultipleChoiceField(
external_enum=StorageCapability,
internal_enum=IntStorageCapability,
**kwargs,
)
class StorageCapabilityListField(
DualEnumsListField,
external_enum=StorageCapability,
internal_enum=IntStorageCapability,
):
pass
29 changes: 29 additions & 0 deletions addon_service/common/enums/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import enum
from typing import ClassVar


def same_enum_names(enum_a: type[enum.Enum], enum_b: type[enum.Enum]) -> bool:
# ensure enums have same names
_names_a = {_item.name for _item in enum_a}
_names_b = {_item.name for _item in enum_b}
return _names_a == _names_b


class IntEnumForEnum(enum.IntEnum):
__base_enum: ClassVar[type[enum.Enum]]

def __init_subclass__(cls, /, base_enum: type[enum.Enum], **kwargs):
super().__init_subclass__(**kwargs)
assert same_enum_names(base_enum, cls)
cls.__base_enum = base_enum

@classmethod
def to_int(cls, base_enum_member):
return cls[base_enum_member.name]

@classmethod
def as_django_choices(cls):
return [(int(_item), _item.name) for _item in cls]

def to_base_enum(self) -> enum.Enum:
return self.__base_enum[self.name]
50 changes: 50 additions & 0 deletions addon_service/common/enums/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import enum
from typing import ClassVar

from rest_framework_json_api import serializers

from addon_service.common.enums import same_enum_names


class DualEnumsListField(serializers.MultipleChoiceField):
"""use one enum in your database and another in your api!"""

__internal_enum: ClassVar[type[enum.Enum]]
__external_enum: ClassVar[type[enum.Enum]]

def __init__(self, **kwargs):
super().__init__(
**kwargs,
choices={ # valid serialized values come from the external enum
_external_member.value for _external_member in self.__external_enum
},
)

def __init_subclass__(
cls,
/,
internal_enum: type[enum.Enum],
external_enum: type[enum.Enum],
**kwargs,
):
super().__init_subclass__(**kwargs)
assert same_enum_names(internal_enum, external_enum)
cls.__internal_enum = internal_enum
cls.__external_enum = external_enum

def to_internal_value(self, data) -> list[enum.Enum]:
_names: set = super().to_internal_value(data)
return [self._to_internal_enum_member(_name) for _name in _names]

def to_representation(self, value):
_member_list = super().to_representation(value)
return {self._to_external_enum_value(_member) for _member in _member_list}

def _to_internal_enum_member(self, external_value) -> enum.Enum:
_external_member = self.__external_enum(external_value)
return self.__internal_enum[_external_member.name]

def _to_external_enum_value(self, internal_value: enum.Enum):
_internal_member = self.__internal_enum(internal_value)
_external_member = self.__external_enum[_internal_member.name]
return _external_member.value
4 changes: 2 additions & 2 deletions addon_service/configured_storage_addon/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework_json_api.relations import ResourceRelatedField
from rest_framework_json_api.utils import get_resource_type_from_model

from addon_service.capability.serializers import StorageCapabilityField
from addon_service.capability.serializers import StorageCapabilityListField
from addon_service.models import (
AuthorizedStorageAccount,
ConfiguredStorageAddon,
Expand All @@ -24,7 +24,7 @@ def to_internal_value(self, data):
class ConfiguredStorageAddonSerializer(serializers.HyperlinkedModelSerializer):
root_folder = serializers.CharField(required=False)
url = serializers.HyperlinkedIdentityField(view_name=f"{RESOURCE_NAME}-detail")
connected_capabilities = StorageCapabilityField()
connected_capabilities = StorageCapabilityListField()
base_account = ResourceRelatedField(
queryset=AuthorizedStorageAccount.objects.all(),
many=False,
Expand Down

0 comments on commit b442bce

Please sign in to comment.