"""Favro Data Model. Based on the descriptions on the [Favro API Specification Page](https://favro.com/developer/), and observed behaviour. """ import dataclasses import datetime from typing import Any @dataclasses.dataclass(frozen=True) class SeqId: raw_id: int @dataclasses.dataclass(frozen=True) class CardId: raw_id: str @dataclasses.dataclass(frozen=True) class CommonId: raw_id: str @dataclasses.dataclass(frozen=True) class UserId: raw_id: str @dataclasses.dataclass(frozen=True) class OrganizationId: raw_id: str @dataclasses.dataclass(frozen=True) class CardAssignment: user: UserId completed: bool @staticmethod def from_json(json: dict[str, Any]) -> 'CardAssignment': return CardAssignment( UserId(json['userId']), json['completed'], ) @dataclasses.dataclass(frozen=True) class UserInfo: user_id: UserId name: str email: str organization_role: str @staticmethod def from_json(json: dict[str, Any]) -> 'UserInfo': return UserInfo( map_opt(UserId, json.get('userId')), json.get('name'), json.get('email'), json.get('organizationRole'), ) @dataclasses.dataclass(frozen=True) class TagId: raw_id: str @dataclasses.dataclass(frozen=True) class CustomFieldId: raw_id: str @dataclasses.dataclass(frozen=True) class TagInfo: tag_id: TagId organization_id: OrganizationId name: str color: str | None @staticmethod def from_json(json: dict[str, Any]) -> 'TagInfo': return TagInfo( TagId(json['tagId']), OrganizationId(json['organizationId']), json['name'], json.get('color'), ) @dataclasses.dataclass(frozen=True) class CustomField: custom_field_id: CustomFieldId value: list[str] | str @staticmethod def from_json(json: dict[str, Any]) -> 'CustomField': return CustomField( CustomFieldId(json['customFieldId']), json.get('value'), ) @dataclasses.dataclass(frozen=True) class CardDependency: card_id: CardId card_common_id: CommonId is_before: bool reverse_card_id: CardId @staticmethod def from_json(json: dict[str, Any]) -> 'CardDependency': return CardDependency( CardId(json['cardId']), CommonId(json['cardCommonId']), json['isBefore'], CardId(json['reverseCardId']), ) @dataclasses.dataclass(frozen=True) class Card: card_id: CardId seq_id: SeqId common_id: CommonId organization_id: OrganizationId is_archived: bool name: str dependencies: list[CardDependency] assignments: list[CardAssignment] custom_fields: list[CustomField] tags: list[TagId] todo_list_user_id: UserId | None todo_list_completed: bool | None creator_user_id: UserId creation_date: datetime.datetime start_date: datetime.date | None due_date: datetime.date | None attachments: list[dict] detailed_description: str | None archived: bool @staticmethod def from_json(json: dict[str, Any]) -> 'Card': return Card( card_id=CardId(json['cardId']), seq_id=SeqId(json['sequentialId']), common_id=CommonId(json['cardCommonId']), detailed_description=json.get('detailedDescription'), is_archived=json['archived'], organization_id=OrganizationId(json['organizationId']), name=json['name'], todo_list_user_id=map_opt(UserId, json.get('todoListUserId')), todo_list_completed=json.get('todoListCompleted'), dependencies=[ CardDependency.from_json(dep) for dep in json['dependencies'] ], tags=[TagId(tag) for tag in json['tags']], creator_user_id=UserId(json['createdByUserId']), creation_date=map_opt(datetime.datetime.fromisoformat,json.get('createdAt')), start_date=map_opt(datetime.datetime.fromisoformat,json.get('startDate')), due_date=map_opt(datetime.datetime.fromisoformat,json.get('dueDate')), assignments=[CardAssignment.from_json(ass) for ass in json['assignments']], custom_fields=[ CustomField.from_json(field) for field in json['customFields'] ], archived=json['archived'], attachments=json['attachments'], # TODO ) def map_opt(mapper, value): if value is not None: return mapper(value) return None