"""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 def map_opt(mapper, value): if value is not None: return mapper(value) return None @dataclasses.dataclass(frozen=True) class CollectionId: raw_id: int @dataclasses.dataclass(frozen=True) class SeqId: raw_id: int @dataclasses.dataclass(frozen=True) class CardId: raw_id: str @dataclasses.dataclass(frozen=True) class CardCommonId: 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 CustomFieldId: raw_id: str @dataclasses.dataclass(frozen=True) class WidgetCommonId: raw_id: str @dataclasses.dataclass(frozen=True) class CustomFieldItemId: raw_id: str @dataclasses.dataclass(frozen=True) class TaskId: raw_id: str @dataclasses.dataclass(frozen=True) class TaskListId: 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 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 CustomFieldItem: """Custom field item object. Fields: - customFieldItemId: The id of the custom field item. - name: The name of the custom field item. """ custom_field_item_id: CustomFieldItemId name: str @staticmethod def from_json(json: dict[str, Any]) -> 'CustomFieldItem': return CustomFieldItem( custom_field_item_id=CustomFieldItemId(json['customFieldItemId']), name=json['name'], ) @dataclasses.dataclass(frozen=True) class CustomFieldInfo: """Custom field object. Fields: - organizationId: The id of the organization that this custom field exists in. - customFieldId: The id of the custom field. - widgetCommonId: The shared id of the widget if the custom field is local to that widget. - type: field type. Refer to allowed custom field types. - name: name of the custom field. - enabled: True if the custom field is currently enabled for the organization. - customFieldItems: The list of items that this custom field can have in case it is a selectable one. """ organization_id: OrganizationId custom_field_id: CustomFieldId widget_common_id: WidgetCommonId type: str name: str enabled: bool custom_field_items: list[CustomFieldItem] def get_field_item( self, field_item_id: CustomFieldItemId, ) -> CustomFieldItem | None: for item in self.custom_field_items: if item.custom_field_item_id == field_item_id: return item return None @staticmethod def from_json(json: dict[str, Any]) -> 'CustomFieldInfo': return CustomFieldInfo( organization_id=OrganizationId(json['organizationId']), custom_field_id=CustomFieldId(json['customFieldId']), widget_common_id=map_opt(WidgetCommonId, json.get('widgetCommonId')), type=json['type'], name=json['name'], enabled=json['enabled'], custom_field_items=[ CustomFieldItem.from_json(f) for f in json.get('customFieldItems', []) ], ) @dataclasses.dataclass(frozen=True) class CustomField: custom_field_id: CustomFieldId value: list[CustomFieldItemId] | CustomFieldItemId color: str @staticmethod def from_json(json: dict[str, Any]) -> 'CustomField': value = json.get('value') if value is None: typed_value = [] elif isinstance(value, str): typed_value = CustomFieldItemId(value) else: typed_value = [CustomFieldItemId(v) for v in value] return CustomField( custom_field_id=CustomFieldId(json['customFieldId']), value=typed_value, color=json.get('color'), ) @dataclasses.dataclass(frozen=True) class CardDependency: card_id: CardId card_common_id: CardCommonId is_before: bool reverse_card_id: CardId @staticmethod def from_json(json: dict[str, Any]) -> 'CardDependency': return CardDependency( CardId(json['cardId']), CardCommonId(json['cardCommonId']), json['isBefore'], CardId(json['reverseCardId']), ) @dataclasses.dataclass(frozen=True) class Task: task_id: TaskId task_list_id: TaskListId organization_id: OrganizationId card_common_id: CardCommonId name: str completed: bool position: int @staticmethod def from_json(json: dict[str, Any]) -> 'Task': return Task( task_id=json['taskId'], task_list_id=json['taskListId'], organization_id=json['organizationId'], card_common_id=json['cardCommonId'], name=json['name'], completed=json['completed'], position=json['position'], ) @dataclasses.dataclass(frozen=True) class Collection: collection_id: CollectionId organization_id: OrganizationId name: str shared_to_users: list[dict[str, str]] # TODO public_sharing: str background: str is_archived: bool widget_common_id: WidgetCommonId @staticmethod def from_json(json: dict[str, Any]) -> 'Collection': return Collection( collection_id=CollectionId(json['collectionId']), organization_id=OrganizationId(json['collectionId']), name=json['name'], shared_to_users=json['sharedToUsers'], public_sharing=json['publicSharing'], background=json['background'], is_archived=json['archived'], widget_common_id=map_opt(WidgetCommonId, json.get('widgetCommonId')), ) @dataclasses.dataclass(frozen=True) class Card: card_id: CardId seq_id: SeqId common_id: CardCommonId 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 @staticmethod def from_json(json: dict[str, Any]) -> 'Card': card_id = CardId(json['cardId']) seq_id = SeqId(json['sequentialId']) return Card( card_id=card_id, seq_id=seq_id, common_id=CardCommonId(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'] ], attachments=json['attachments'], # TODO )