PynamoDB
PynamoDB copied to clipboard
unable to create ListAttribute of custom attribute
Steps to reproduce
- Following class definition Attribute definition
UnicodeEnumAttribute
from enum import Enum
from typing import Any
from typing import Optional
from typing import Type
from typing import TypeVar
import pynamodb.constants
from pynamodb.attributes import Attribute
from app.config.llmconfig import AvailableLLMEnum
T = TypeVar("T", bound=Enum)
_fail: Any = object()
class UnicodeEnumAttribute(Attribute[T]):
attr_type = pynamodb.constants.STRING
def __init__(
self, enum_type: Type[T], unknown_value: Optional[T] = _fail, **kwargs: Any
) -> None:
"""
:param enum_type: The type of the enum
"""
super().__init__(**kwargs)
self.enum_type = enum_type
self.unknown_value = unknown_value
if not all(isinstance(e.value, str) for e in self.enum_type):
raise TypeError(
f"Enumeration '{self.enum_type}' values must be all strings",
)
def deserialize(self, value: str) -> Optional[T]:
try:
return self.enum_type(value)
except ValueError:
if self.unknown_value is _fail:
raise
return self.unknown_value
def serialize(self, value: T) -> str:
if not isinstance(value, self.enum_type):
raise TypeError(
f"value has invalid type '{type(value)}'; expected '{self.enum_type}'",
)
return value.value
Model defintion
from typing import Optional
from pynamodb.attributes import UnicodeAttribute, ListAttribute, NumberAttribute
from app.config.dynamodbconfig import (
DynamoDbTableEnum,
DYNAMO_ENDPOINT,
DYNAMO_REGION,
)
from app.config.llmconfig import AvailableLLMEnum
from app.models.basemodel import BaseModel
from app.models.custom_attributes.unicodeenum import UnicodeEnumAttribute, AvailableLLMUnicodeEnumListAttribute
class Channel(BaseModel):
class Meta:
table_name = DynamoDbTableEnum.CHANNELS_TABLE_NAME
host = DYNAMO_ENDPOINT
region = DYNAMO_REGION
name = UnicodeAttribute(hash_key=True)
# list of enums cannot be generated with the way of pynamo implements it, bec
whitelisted_llms = ListAttribute(default=[], of=type(UnicodeEnumAttribute(AvailableLLMEnum)))
temperature = NumberAttribute(null=True)
presence_penalty = NumberAttribute(null=True)
@staticmethod
def get_channel_name(tenant_name: str, channel_name: str):
return tenant_name + "|" + channel_name
- try saving an item
- issue
{"@timestamp":"2023-09-05T04:53:52.442Z","log.level":"error","message":"(TypeError(\"__init__() missing 1 required positional argument: 'enum_type'\"),)","ecs":{"version":"1.6.0"},"error":{"message":"unhandled errors in a TaskGroup (1 sub-exception)","stack_trace":" File \"/Users/rajashekar/workspace/gai/maverick/venv/lib/python3.9/site-packages/starlette/middleware/errors.py\", line 162, in __call__\n await self.app(scope, receive, _send)\n File \"/Users/rajashekar/workspace/gai/maverick/venv/lib/python3.9/site-packages/starlette/middleware/base.py\", line 110, in __call__\n response_sent.set()\n File \"/Users/rajashekar/workspace/gai/maverick/venv/lib/python3.9/site-packages/anyio/_backends/_asyncio.py\", line 664, in __aexit__\n raise BaseExceptionGroup(\n","type":"ExceptionGroup"},"log":{"logger":"app.exception_handlers","origin":{"file":{"line":1,"name":"<string>"},"function":"<module>"},"original":"(TypeError(\"__init__() missing 1 required positional argument: 'enum_type'\"),)"},"process":{"name":"SpawnProcess-3","pid":99594,"thread":{"id":8658525696,"name":"MainThread"}}}
Suspected cause
https://github.com/pynamodb/PynamoDB/blob/335c7cde6732c5121347207e60479d96e47338f6/pynamodb/attributes.py#L1396
Temporary Workaround
Custom Attribute
class AvailableLLMUnicodeEnumListAttribute(UnicodeEnumAttribute[AvailableLLMEnum]):
def __init__(self, **kwargs: Any) -> None:
"""
:param enum_type: The type of the enum
"""
super().__init__(AvailableLLMEnum, **kwargs)
Model
class Channel(BaseModel):
class Meta:
table_name = DynamoDbTableEnum.CHANNELS_TABLE_NAME
host = DYNAMO_ENDPOINT
region = DYNAMO_REGION
name = UnicodeAttribute(hash_key=True)
# list of enums cannot be generated with the way of pynamo implements it, bec
whitelisted_llms = ListAttribute(default=[], of=AvailableLLMUnicodeEnumListAttribute)
temperature = NumberAttribute(null=True)
presence_penalty = NumberAttribute(null=True)
@staticmethod
def get_channel_name(tenant_name: str, channel_name: str):
return tenant_name + "|" + channel_name
@ikonst is this the right way to achieve this ??
Your solution seems reasonable.