Начнем с сути проблемы: есть апишка (всё та же, но не суть), все ответы которой представляются в сдк как объекты/модели (можно сказать, аналог ORM):
class User: first_name = None last_name = None position = None users = group_and_users_client.list_users() # сырой ответ от API: [{"userId": "xxx", "firstName": "John", "lastName": "Connor", "position": "Tester"}] # в переменной users будет список объектов типа User такого вида: for user in users: print(user.first_name, user.last_name, user.position)
Есть Enum Positions:
from aenum import Enum, extend_enum class Position( str, Enum ): DEV = "DEV" TESTER = "TESTER" MANAGER = "MANAGER" @classmethod def _missing_(cls, value): value_upper_case = value.upper() try: return getattr(cls, value_upper_case) except: extend_enum(Position, value_upper_case, value_upper_case) return getattr(cls, value_upper_case)
То есть, если нет позиции в списке, то список расширяется спокойно. Даже не спрашивайте, почему так )
Фокус в том, что есть наследование от класса User:
class Dev(User): position = 'DEV' class Tester(User): position = 'TESTER' class MANAGER(User): position = 'MANAGER'
И вот, если есть позиция в Enum, то возвращается соответствующая модель, а если позиции нет в списке, то возвращается базовый класс User, где position = Position(“any_position”)
Итого, получается, что в тех известных моделях, position - строчка (str), а в базовой модели - Position.
Выходит наша sdk возвращает несовместимые типы позиций:
for user in users: print(user.position, type(user.position)) # выхлоп (AQA - Automation QA): DEV <class 'str'> TESTER <class 'str'> Position.AQA <aenum 'Position'>
И вот задача, надо сделать типы совестимыми, но, блин, без breaking change!!!
То есть, выходит, что вдруг кто-то решал для себя проблему таким образом:
if isinstance(user.position, str): print(user.position) else: print(user.position.value) # или наоборот if isinstance(user.position, Position): print(user.position.value) else: print(user.position)
то, надо сделать так, чтобы и оно работало и просто все position были строчками!!!
Пока что мне удалось придумать лишь половину решения:
class PositionWrapper(str): def __new__(cls, position): if isinstance(position, Enum): return str.__new__(cls, position.value) return str.__new__(cls, position) def __init__(self, position): self.position = position def __str__(self): return self.value @property def value(self): if isinstance(self.position, Enum): return self.position.value return self.position
А вот наследоваться от Position никак нельзя:
File "/Users/bserhii/Projects/work/okta/venv/lib/python3.8/site-packages/aenum/__init__.py", line 1638, in __prepare__ member_type, first_enum = metacls._get_mixins_(bases) File "/Users/bserhii/Projects/work/okta/venv/lib/python3.8/site-packages/aenum/__init__.py", line 2263, in _get_mixins_ raise TypeError("cannot extend enumerations via subclassing") TypeError: cannot extend enumerations via subclassing
В общем, что делать?