import requests import dataclasses import datetime from typing import Any import functools from collections.abc import Iterator PREFIX = 'PAR-' # TODO: Make configurable # Types @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 Card: card_id: CardId seq_id: SeqId common_id: CommonId organization_id: OrganizationId is_archived: bool name: str dependencies: list[None] # TODO tags: list[None] # TODO todo_list_user_id: UserId | None todo_list_completed: bool | None creator_user_id: UserId creation_date: datetime.datetime detailed_description: str | None ''' TODO, fieds: 'position': -399 'listPosition': -399 'isLane': False 'assignments': [{'userId': 'Faieomp8fuS8DrnyP' 'completed': True}] 'tasksTotal': 0 'tasksDone': 0 'attachments': [] 'customFields': 'timeOnBoard': None 'timeOnColumns': None 'favroAttachments': [] ''' @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 = UserId(json['todoListUserId']), todo_list_completed = json['todoListCompleted'], dependencies = json['dependencies'], tags = json['tags'], creator_user_id = UserId(json['createdByUserId']), creation_date = datetime.datetime.fromisoformat(json['createdAt']), ) # Endpoints URL_API_ROOT = 'https://favro.com/api/v1' URL_GET_ALL_CARDS = URL_API_ROOT+'/cards' URL_UPDATE_CARD = URL_API_ROOT+'/cards/{card_id}' class FavroClient: def __init__(self, *, favro_org_id: OrganizationId, favro_username: str, favro_password: str, session: requests.Session | None = None): assert favro_org_id is not None assert favro_username is not None assert favro_password is not None # Setup session self.session = session or requests.Session() self.session.auth = (favro_username, favro_password) self.session.headers.update({ 'organizationId': favro_org_id.raw_id, 'content-type': 'application/json', }) def check_logged_in(self) -> None: next(self.get_todo_list_cards()) def get_todo_list_cards(self) -> Iterator[Card]: yield from self.get_cards(todo_list=True) def get_cards(self, *, seq_id: SeqId | None = None, todo_list=True) -> Iterator[Card]: # Determine params for get_cards params = {'descriptionFormat': 'markdown'} if seq_id: params['cardSequentialId']= str(seq_id.raw_id) if todo_list: params['todoList'] = 'true' # Run query response = self.session.get(URL_GET_ALL_CARDS, params = params) response.raise_for_status() json = response.json() # TODO: Pageination for entity_json in json['entities']: yield Card.from_json(entity_json) del entity_json def get_card(self, seq_id: SeqId) -> Card: return next(self.get_cards(seq_id=seq_id)) def get_card_id(self, seq_id: SeqId) -> CardId: first_card = next(self.get_cards(seq_id = seq_id)) return first_card.card_id def update_card_description(self, card_id: CardId, description: str) -> Card: """Returns updated Card.""" json_body = { 'detailedDescription': description, 'descriptionFormat': 'markdown', } response = self.session.put(URL_UPDATE_CARD.format(card_id=card_id.raw_id), json=json_body) response.raise_for_status() return Card.from_json(response.json())