From b442bced6fec0f3a4662ca019823af26987de375 Mon Sep 17 00:00:00 2001 From: Abram Booth Date: Thu, 8 Feb 2024 12:01:28 -0500 Subject: [PATCH] tidy enum utils --- .../authorized_storage_account/serializers.py | 4 +- addon_service/capability/models.py | 29 +---------- addon_service/capability/serializers.py | 51 ++++--------------- addon_service/common/enums/__init__.py | 29 +++++++++++ addon_service/common/enums/serializers.py | 50 ++++++++++++++++++ .../configured_storage_addon/serializers.py | 4 +- 6 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 addon_service/common/enums/__init__.py create mode 100644 addon_service/common/enums/serializers.py diff --git a/addon_service/authorized_storage_account/serializers.py b/addon_service/authorized_storage_account/serializers.py index 002c8fd5..85076b70 100644 --- a/addon_service/authorized_storage_account/serializers.py +++ b/addon_service/authorized_storage_account/serializers.py @@ -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, @@ -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(), diff --git a/addon_service/capability/models.py b/addon_service/capability/models.py index 71779fdf..f7897dc4 100644 --- a/addon_service/capability/models.py +++ b/addon_service/capability/models.py @@ -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 diff --git a/addon_service/capability/serializers.py b/addon_service/capability/serializers.py index d73bda88..d380d60e 100644 --- a/addon_service/capability/serializers.py +++ b/addon_service/capability/serializers.py @@ -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 diff --git a/addon_service/common/enums/__init__.py b/addon_service/common/enums/__init__.py new file mode 100644 index 00000000..bbcd878a --- /dev/null +++ b/addon_service/common/enums/__init__.py @@ -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] diff --git a/addon_service/common/enums/serializers.py b/addon_service/common/enums/serializers.py new file mode 100644 index 00000000..455f998b --- /dev/null +++ b/addon_service/common/enums/serializers.py @@ -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 diff --git a/addon_service/configured_storage_addon/serializers.py b/addon_service/configured_storage_addon/serializers.py index c1e2b004..486bcda1 100644 --- a/addon_service/configured_storage_addon/serializers.py +++ b/addon_service/configured_storage_addon/serializers.py @@ -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, @@ -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,