diff --git a/favro_sync/__main__.py b/favro_sync/__main__.py index a322f99..11ee231 100644 --- a/favro_sync/__main__.py +++ b/favro_sync/__main__.py @@ -1,12 +1,11 @@ - -import tempfile import logging +import tempfile import requests_cache +from . import secrets from .favro_client import FavroClient, OrganizationId from .favro_fuse import start_favro_fuse -from . import secrets def main(): @@ -15,7 +14,10 @@ def main(): read_only = False with tempfile.TemporaryDirectory(prefix='favro_sync_') as tmpdirname: - session = requests_cache.CachedSession( tmpdirname + '/http-cache.sqlite', expire_after=10,) + session = requests_cache.CachedSession( + tmpdirname + '/http-cache.sqlite', + expire_after=10, + ) client = FavroClient( favro_org_id=OrganizationId(secrets.favro_org_id()), diff --git a/favro_sync/favro_client.py b/favro_sync/favro_client.py index e402e58..feaeed2 100644 --- a/favro_sync/favro_client.py +++ b/favro_sync/favro_client.py @@ -1,11 +1,19 @@ +import dataclasses from collections.abc import Iterator from logging import getLogger import requests -import dataclasses -from .favro_data_model import (Card, CardId, OrganizationId, SeqId, UserInfo, - UserId, UserInfo, TagId, TagInfo) +from .favro_data_model import ( + Card, + CardId, + OrganizationId, + SeqId, + TagId, + TagInfo, + UserId, + UserInfo, +) from .favro_markdown import CardContents logger = getLogger(__name__) @@ -14,11 +22,11 @@ logger = getLogger(__name__) 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}' -URL_GET_USER = URL_API_ROOT + '/users/{user_id}' -URL_GET_TAG = URL_API_ROOT + '/tags/{tag_id}' +URL_GET_USER = URL_API_ROOT + '/users/{user_id}' +URL_GET_TAG = URL_API_ROOT + '/tags/{tag_id}' + class CardCache: - def __init__(self): self.cards = [] @@ -44,6 +52,7 @@ class CardCache: return card return None + class FavroClient: def __init__( self, @@ -78,7 +87,10 @@ class FavroClient: yield from self.get_cards(todo_list=True) def get_cards( - self, *, seq_id: SeqId | None = None, todo_list=False, + self, + *, + seq_id: SeqId | None = None, + todo_list=False, ) -> Iterator[Card]: # Determine params for get_cards request = self._get_cards_request(seq_id, todo_list) @@ -97,7 +109,9 @@ class FavroClient: del entity_json def _get_cards_request( - self, seq_id: SeqId | None = None, todo_list=False, + self, + seq_id: SeqId | None = None, + todo_list=False, ) -> requests.PreparedRequest: params = {'descriptionFormat': 'markdown'} if seq_id is not None: @@ -128,13 +142,21 @@ class FavroClient: requests=[self._get_cards_request(seq_id=card.seq_id)], ) - def update_card_contents_locally(self, card_id: CardId, card_contents: CardContents) -> Card: + def update_card_contents_locally( + self, card_id: CardId, card_contents: CardContents, + ) -> Card: if card := self.cache.remove(card_id): - card = dataclasses.replace(card, detailed_description=card_contents.description,name=card_contents.name) + card = dataclasses.replace( + card, + detailed_description=card_contents.description, + name=card_contents.name, + ) self.cache.add_card(card) return card - def update_card_contents(self, card_id: CardId, card_contents: CardContents) -> Card: + def update_card_contents( + self, card_id: CardId, card_contents: CardContents, + ) -> Card: """Returns updated Card.""" if self.read_only == 'silent': logger.warning( @@ -154,7 +176,8 @@ class FavroClient: url = URL_UPDATE_CARD.format(card_id=card_id.raw_id) logger.warning('Sending request: %s', url) response = self.session.put( - url, json=json_body, + url, + json=json_body, ) response.raise_for_status() self._invalidate_cache(card_id) diff --git a/favro_sync/favro_data_model.py b/favro_sync/favro_data_model.py index a0bbceb..84e93fb 100644 --- a/favro_sync/favro_data_model.py +++ b/favro_sync/favro_data_model.py @@ -36,10 +36,11 @@ class CardAssignment: @staticmethod def from_json(json: dict[str, Any]) -> 'CardAssignment': return CardAssignment( - UserId(json['userId']), - json['completed'], + UserId(json['userId']), + json['completed'], ) + @dataclasses.dataclass(frozen=True) class UserInfo: user_id: UserId @@ -50,16 +51,18 @@ class UserInfo: @staticmethod def from_json(json: dict[str, Any]) -> 'UserInfo': return UserInfo( - UserId(json['userId']), - json['name'], - json['email'], - json['organizationRole'], + UserId(json['userId']), + json['name'], + json['email'], + json['organizationRole'], ) + @dataclasses.dataclass(frozen=True) class TagId: raw_id: str + @dataclasses.dataclass(frozen=True) class TagInfo: tag_id: TagId @@ -70,12 +73,13 @@ class TagInfo: @staticmethod def from_json(json: dict[str, Any]) -> 'TagInfo': return TagInfo( - TagId(json['tagId']), - OrganizationId(json['organizationId']), - json['name'], - json.get('color'), + TagId(json['tagId']), + OrganizationId(json['organizationId']), + json['name'], + json.get('color'), ) + @dataclasses.dataclass(frozen=True) class CardDependency: card_id: CardId @@ -86,12 +90,13 @@ class CardDependency: @staticmethod def from_json(json: dict[str, Any]) -> 'CardDependency': return CardDependency( - CardId(json['cardId']), - CommonId(json['cardCommonId']), - json['isBefore'], - CardId(json['reverseCardId']), + CardId(json['cardId']), + CommonId(json['cardCommonId']), + json['isBefore'], + CardId(json['reverseCardId']), ) + @dataclasses.dataclass(frozen=True) class Card: card_id: CardId @@ -112,7 +117,9 @@ class Card: @staticmethod def from_json(json: dict[str, Any]) -> 'Card': - todo_list_user_id = UserId(json['todoListUserId']) if 'todoListUserId' in json else None + todo_list_user_id = ( + UserId(json['todoListUserId']) if 'todoListUserId' in json else None + ) return Card( card_id=CardId(json['cardId']), seq_id=SeqId(json['sequentialId']), @@ -127,5 +134,5 @@ class Card: tags=[TagId(tag) for tag in json['tags']], creator_user_id=UserId(json['createdByUserId']), creation_date=datetime.datetime.fromisoformat(json['createdAt']), - assignments = [CardAssignment.from_json(ass) for ass in json['assignments']], + assignments=[CardAssignment.from_json(ass) for ass in json['assignments']], ) diff --git a/favro_sync/favro_fuse.py b/favro_sync/favro_fuse.py index 4200060..5d1eb75 100644 --- a/favro_sync/favro_fuse.py +++ b/favro_sync/favro_fuse.py @@ -15,6 +15,7 @@ logger = getLogger(__name__) fuse.fuse_python_api = (0, 2) + class FavroStat(fuse.Stat): def __init__(self): self.st_mode = 0 @@ -53,10 +54,13 @@ class RootThing(Thing): class CardThing(Thing): seq_id: SeqId + class FavroFuse(fuse.Fuse): """Favro Filesystem in Userspace.""" - def __init__(self, favro_client: FavroClient, formatter: CardFileFormatter, **kwargs): + def __init__( + self, favro_client: FavroClient, formatter: CardFileFormatter, **kwargs, + ): self.favro_client = favro_client self.formatter = formatter super().__init__(**kwargs) @@ -81,7 +85,7 @@ class FavroFuse(fuse.Fuse): return st def readdir(self, path: str, offset: int) -> Iterator[fuse.Direntry]: - logger.warning('readdir(path=%s, offset=%s)',path, offset) + logger.warning('readdir(path=%s, offset=%s)', path, offset) yield fuse.Direntry('.') yield fuse.Direntry('..') @@ -160,7 +164,10 @@ class FavroFuse(fuse.Fuse): def _format_card_file(self, card: Card) -> str: tags = [self.favro_client.get_tag(tag_id).name for tag_id in card.tags] - assignments = [self.favro_client.get_user(assignment.user).name for assignment in card.assignments] + assignments = [ + self.favro_client.get_user(assignment.user).name + for assignment in card.assignments + ] card_contents = CardContents( card.name, card.detailed_description, @@ -169,6 +176,7 @@ class FavroFuse(fuse.Fuse): ) return self.formatter.format_card_contents(card_contents) + def splice(original_buffer: bytes, input_buffer: bytes, offset: int) -> bytes: return ( original_buffer[0 : offset - 1] diff --git a/favro_sync/favro_markdown.py b/favro_sync/favro_markdown.py index 5a3b094..00d37ac 100644 --- a/favro_sync/favro_markdown.py +++ b/favro_sync/favro_markdown.py @@ -1,15 +1,9 @@ import dataclasses -import errno -import re -import stat -from collections.abc import Iterator -from logging import getLogger import frontmatter import marko import marko.md_renderer -from .favro_data_model import Card, SeqId @dataclasses.dataclass(frozen=True) class CardContents: @@ -18,10 +12,11 @@ class CardContents: tags: list[str] assignments: list[str] + class CardFileFormatter: """Component for formatting and parsing card files.""" - def __init__(self, obsidian_mode = True): + def __init__(self, obsidian_mode=True): self.obsidian_mode = obsidian_mode self.markdown = marko.Markdown() self.renderer = marko.md_renderer.MarkdownRenderer() @@ -36,11 +31,13 @@ class CardFileFormatter: if card.tags: frontmatter_data['tags'] = card.tags if self.obsidian_mode: - frontmatter_data['tags'] = ['#'+t for t in frontmatter_data['tags']] + frontmatter_data['tags'] = ['#' + t for t in frontmatter_data['tags']] if card.assignments: frontmatter_data['assignments'] = card.assignments if self.obsidian_mode: - frontmatter_data['assignments'] = [f'[[{name}]]' for name in frontmatter_data['assignments']] + frontmatter_data['assignments'] = [ + f'[[{name}]]' for name in frontmatter_data['assignments'] + ] # Frontmatter if frontmatter_data: @@ -84,9 +81,8 @@ class CardFileFormatter: description = self.renderer.render_children(document).strip() return CardContents( - name, - description, - tags = [], - assignments = [], + name, + description, + tags=[], + assignments=[], ) - diff --git a/favro_sync/secrets.py b/favro_sync/secrets.py index 30cddd3..3fa7847 100644 --- a/favro_sync/secrets.py +++ b/favro_sync/secrets.py @@ -2,11 +2,14 @@ import secret_loader secrets = secret_loader.SecretLoader() + def favro_org_id(): return secrets.load_or_fail('FAVRO_ORGANIZATION_ID') + def favro_username(): return secrets.load_or_fail('FAVRO_USERNAME') + def favro_password(): return secrets.load_or_fail('FAVRO_PASSWORD') diff --git a/test/test_client.py b/test/test_client.py index 367376b..61cbb54 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -1,10 +1,9 @@ - from favro_sync import secrets -from favro_sync .favro_client import FavroClient, OrganizationId -from favro_sync .favro_fuse import start_favro_fuse +from favro_sync.favro_client import FavroClient, OrganizationId # TODO: Skip if no secrets + def test_create_client(): client = FavroClient( favro_org_id=OrganizationId(secrets.favro_org_id()), @@ -16,6 +15,7 @@ def test_create_client(): client.check_logged_in() return client + def test_get_card(): client = test_create_client() card = next(client.get_todo_list_cards()) diff --git a/test/test_markdown_parsing.py b/test/test_markdown_parsing.py index 79b480e..09646ce 100644 --- a/test/test_markdown_parsing.py +++ b/test/test_markdown_parsing.py @@ -1,6 +1,6 @@ from favro_sync.favro_markdown import CardFileFormatter -EXAMPLE_TEXT_1 = ''' +EXAMPLE_TEXT_1 = """ --- aliases: - Hello World @@ -15,10 +15,11 @@ Test description 1. Derp 2. Derp 3. Derp -'''.strip() +""".strip() FORMATTER = CardFileFormatter() + def test_parse_and_render(): card_contents = FORMATTER.parse_card_contents(EXAMPLE_TEXT_1)