from dataclasses import asdict, dataclass
from q2_smart.exceptions import Q2SmartInternalError
[docs]
@dataclass
class TraitList:
traits: list[TraitMetadata]
[docs]
@classmethod
def from_response(cls, payload: object) -> "TraitList":
if not isinstance(payload, dict) or not isinstance(payload.get("traits"), list):
raise Q2SmartInternalError(
"Invalid GET traits response: 'traits' must be a list"
)
traits = []
text_fields = ("id", "display_name", "description", "type", "source")
count_fields = ("users_count", "total_users")
required = (*text_fields, *count_fields)
for index, trait in enumerate(payload["traits"]):
if not isinstance(trait, dict):
raise Q2SmartInternalError(
f"Invalid GET traits response: trait at index {index} must be an object"
)
if "hidden" not in trait:
raise Q2SmartInternalError(
f"Invalid GET traits response: trait at index {index} is missing hidden"
)
if not isinstance(trait["hidden"], bool):
raise Q2SmartInternalError(
f"Invalid GET traits response: trait at index {index} has non-boolean hidden"
)
if trait["hidden"]:
continue
missing = [field for field in required if field not in trait]
if missing:
raise Q2SmartInternalError(
f"Invalid GET traits response: trait at index {index} is missing {', '.join(missing)}"
)
for field in text_fields:
value = trait[field]
if not isinstance(value, str) and not (
field == "source" and value is None
):
raise Q2SmartInternalError(
f"Invalid GET traits response: trait at index {index} has invalid {field}"
)
for field in count_fields:
if type(trait[field]) is not int or trait[field] < 0:
raise Q2SmartInternalError(
f"Invalid GET traits response: trait at index {index} has invalid {field}"
)
traits.append(TraitMetadata(**{field: trait[field] for field in required}))
return cls(traits)
[docs]
def to_json(self) -> dict:
return {"traits": [trait.to_dict() for trait in self.traits]}