1
0
favro-sync/favro_sync/favro_client.py

154 lines
4.4 KiB
Python

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
# Derived
seq_id_with_prefix: str
''' 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']),
seq_id_with_prefix = PREFIX + str(json['sequentialId']),
)
# 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, *, seqid: SeqId | None = None, todo_list=True) -> Iterator[Card]:
# Determine params for get_cards
params = {'descriptionFormat': 'markdown'}
if seqid:
params['cardSequentialId']= str(seqid.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, seqid: SeqId) -> Card:
return next(self.get_cards(seqid=seqid))
def get_card_id(self, seqid: SeqId) -> CardId:
first_card = next(self.get_cards(seqid = seqid))
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())