I want to get a list of all the attributes of the class including the attributes used in sub_objects of this class.
Example:
@dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
@dataclass
class People:
id: str
name: str
phones: Phones
I have People class and one of its attributes is of type Phones
.
I want to return this list:
['id', 'name', 'mobile', 'work_phone']
I tried __dict__
, __annotations__
, dir()
and more staff but I can't find a way to do it generic and dynamic. My solution is to do a convertor and return this list hardcoded which seems as a bad idea for maintenance.
I want all the attributes with primitive type. (For example I don't want to include phones
.)
CodePudding user response:
Recursion? You dont need the sbNative thing, its just my module for clean logging.
from dataclasses import dataclass, is_dataclass
from typing import Optional
from sbNative.debugtools import log
@dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
@dataclass
class People:
id: str
name: str
phones: Phones
def find_dataclasses(cls):
classes = []
for obj in cls.__annotations__.values():
if is_dataclass(obj):
classes = find_dataclasses(obj)
classes.append(obj)
return classes
if __name__ == "__main__":
log(*find_dataclasses(People))
CodePudding user response:
Thanks to https://stackoverflow.com/users/13526701/noblockhit
I managed to achieve what I wanted with the next code:
def list_attributes(entity: object) -> List[str]:
"""
@returns: List of all the primitive attributes
"""
attributes: List[str] = []
entity_attributes = entity.__annotations__.items()
for attribute_name, attribute_type in entity_attributes:
if is_dataclass(attribute_type):
attributes = list_attributes(attribute_type)
else:
attributes.append(attribute_name)
return attributes
CodePudding user response:
This is good, but unfortunately it won't work if you have a more complex annotation, such as a container type like List[Phones]
. For example:
@dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
@dataclass
class People:
id: str
name: str
phones: List[Phones]
The current output will be: ['id', 'name', 'phones']
. But, note that we want to exclude the field phones
, and include all the fields of the Phone class, mobile
and work_phone
.
To handle such types, you can use typing.get_args()
and iterate over each of the subscripted types, checking if each type is a dataclass.
from dataclasses import dataclass, is_dataclass
from typing import Optional, List, get_args
@dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
@dataclass
class People:
id: str
name: str
phones: List[Phones]
def list_attributes(entity: object) -> List[str]:
"""
@returns: List of all the primitive attributes
"""
attributes: List[str] = []
entity_attributes = entity.__annotations__.items()
for attribute_name, attribute_type in entity_attributes:
args = get_args(attribute_type)
if args:
for arg in args:
if is_dataclass(arg):
attributes = list_attributes(arg)
break
else:
attributes.append(attribute_name)
elif is_dataclass(attribute_type):
attributes = list_attributes(attribute_type)
else:
attributes.append(attribute_name)
return attributes
print(list_attributes(People))
The above modified version correctly outputs the desired result:
['id', 'name', 'mobile', 'work_phone']