From 1a777c6276cfb38bf1ba151dfc8579f7457b455c Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 18 Nov 2023 10:16:14 +0100 Subject: [PATCH] Removed Todoist backend and migrated responses to schemas. The Todoist Websocket interface is no longer working properly, and I'm sick of reverse engineering it. Closes: #305 --- docs/source/backends.rst | 1 - docs/source/events.rst | 1 - docs/source/platypush/backend/todoist.rst | 5 - docs/source/platypush/events/todoist.rst | 5 - docs/source/platypush/responses/todoist.rst | 5 - docs/source/responses.rst | 1 - platypush/backend/todoist/__init__.py | 145 --------- platypush/backend/todoist/manifest.yaml | 16 - platypush/message/event/todoist.py | 59 ---- platypush/message/response/todoist.py | 326 -------------------- platypush/plugins/todoist/__init__.py | 102 +++--- platypush/schemas/todoist.py | 305 ++++++++++++++++++ 12 files changed, 368 insertions(+), 603 deletions(-) delete mode 100644 docs/source/platypush/backend/todoist.rst delete mode 100644 docs/source/platypush/events/todoist.rst delete mode 100644 docs/source/platypush/responses/todoist.rst delete mode 100644 platypush/backend/todoist/__init__.py delete mode 100644 platypush/backend/todoist/manifest.yaml delete mode 100644 platypush/message/event/todoist.py delete mode 100644 platypush/message/response/todoist.py create mode 100644 platypush/schemas/todoist.py diff --git a/docs/source/backends.rst b/docs/source/backends.rst index 142d687878..8d8253bd3a 100644 --- a/docs/source/backends.rst +++ b/docs/source/backends.rst @@ -38,7 +38,6 @@ Backends platypush/backend/stt.picovoice.hotword.rst platypush/backend/stt.picovoice.speech.rst platypush/backend/tcp.rst - platypush/backend/todoist.rst platypush/backend/weather.buienradar.rst platypush/backend/weather.darksky.rst platypush/backend/weather.openweathermap.rst diff --git a/docs/source/events.rst b/docs/source/events.rst index e5ed0dc359..cf096d81ee 100644 --- a/docs/source/events.rst +++ b/docs/source/events.rst @@ -67,7 +67,6 @@ Events platypush/events/stt.rst platypush/events/sun.rst platypush/events/tensorflow.rst - platypush/events/todoist.rst platypush/events/torrent.rst platypush/events/trello.rst platypush/events/video.rst diff --git a/docs/source/platypush/backend/todoist.rst b/docs/source/platypush/backend/todoist.rst deleted file mode 100644 index 876b6f783e..0000000000 --- a/docs/source/platypush/backend/todoist.rst +++ /dev/null @@ -1,5 +0,0 @@ -``todoist`` -============================= - -.. automodule:: platypush.backend.todoist - :members: diff --git a/docs/source/platypush/events/todoist.rst b/docs/source/platypush/events/todoist.rst deleted file mode 100644 index 8e574f5b5c..0000000000 --- a/docs/source/platypush/events/todoist.rst +++ /dev/null @@ -1,5 +0,0 @@ -``todoist`` -=================================== - -.. automodule:: platypush.message.event.todoist - :members: diff --git a/docs/source/platypush/responses/todoist.rst b/docs/source/platypush/responses/todoist.rst deleted file mode 100644 index d20c5f4114..0000000000 --- a/docs/source/platypush/responses/todoist.rst +++ /dev/null @@ -1,5 +0,0 @@ -``todoist`` -====================================== - -.. automodule:: platypush.message.response.todoist - :members: diff --git a/docs/source/responses.rst b/docs/source/responses.rst index 205f321adf..584dc5893e 100644 --- a/docs/source/responses.rst +++ b/docs/source/responses.rst @@ -17,6 +17,5 @@ Responses platypush/responses/ssh.rst platypush/responses/stt.rst platypush/responses/tensorflow.rst - platypush/responses/todoist.rst platypush/responses/translate.rst platypush/responses/weather.buienradar.rst diff --git a/platypush/backend/todoist/__init__.py b/platypush/backend/todoist/__init__.py deleted file mode 100644 index ab2a57577b..0000000000 --- a/platypush/backend/todoist/__init__.py +++ /dev/null @@ -1,145 +0,0 @@ -import json -import time - -from platypush.backend import Backend -from platypush.context import get_plugin -from platypush.message.event.todoist import ( - NewItemEvent, - RemovedItemEvent, - ModifiedItemEvent, - CheckedItemEvent, - ItemContentChangeEvent, - TodoistSyncRequiredEvent, -) - -from platypush.plugins.todoist import TodoistPlugin - - -class TodoistBackend(Backend): - """ - This backend listens for events on a Todoist account. - """ - - def __init__(self, api_token: str = None, **kwargs): - super().__init__(**kwargs) - self._plugin: TodoistPlugin = get_plugin('todoist') - - if not api_token: - assert ( - self._plugin and self._plugin.api_token - ), 'No api_token specified either on Todoist backend or plugin' - self.api_token = self._plugin.api_token - else: - self.api_token = api_token - - self.url = self._plugin.get_user().output['websocket_url'] - self._ws = None - self._connected = False - self._todoist_initialized = False - - self._items = {} - self._event_handled = False - - def _on_msg(self): - def hndl(*args): - msg = args[1] if len(args) > 1 else args[0] - msg = json.loads(msg) - if msg.get('type') == 'sync_needed': - self._refresh_all() - - return hndl - - def _retry_hndl(self): - self._ws = None - self.logger.warning('Todoist websocket connection closed') - self._connected = False - - while not self._connected: - self._connect() - time.sleep(10) - - def _on_error(self): - def hndl(*args): - error = args[1] if len(args) > 1 else args[0] - self.logger.warning('Todoist websocket error: {}'.format(error)) - self._retry_hndl() - - return hndl - - def _on_close(self): - def hndl(*_): - self.logger.info('Todoist websocket closed') - self._retry_hndl() - - return hndl - - def _on_open(self): - # noinspection PyUnusedLocal - def hndl(ws): - self._connected = True - self.logger.info('Todoist websocket connected') - - if not self._todoist_initialized: - self._refresh_all() - self._todoist_initialized = True - - return hndl - - def _connect(self): - import websocket - - if not self._ws: - self._ws = websocket.WebSocketApp( - self.url, - on_message=self._on_msg(), - on_error=self._on_error(), - on_close=self._on_close(), - ) - - def _refresh_items(self): - new_items = {i['id']: i for i in self._plugin.get_items().output} - - if self._todoist_initialized: - for id, item in new_items.items(): - if id not in self._items.keys(): - self._event_handled = True - self.bus.post(NewItemEvent(item)) - - for id, item in self._items.items(): - if id not in new_items.keys(): - self._event_handled = True - self.bus.post(RemovedItemEvent(item)) - elif new_items[id] != item: - if new_items[id]['checked'] != item['checked']: - self._event_handled = True - self.bus.post(CheckedItemEvent(new_items[id])) - elif new_items[id]['is_deleted'] != item['is_deleted']: - self._event_handled = True - self.bus.post(RemovedItemEvent(new_items[id])) - elif new_items[id]['content'] != item['content']: - self._event_handled = True - self.bus.post(ItemContentChangeEvent(new_items[id])) - else: - self._event_handled = True - self.bus.post(ModifiedItemEvent(new_items[id])) - - self._items = new_items - - def _refresh_all(self): - self._event_handled = False - self._plugin.sync() - self._refresh_items() - - if not self._event_handled: - self.bus.post(TodoistSyncRequiredEvent()) - - def run(self): - super().run() - self.logger.info('Started Todoist backend') - - self._connect() - self._ws.on_open = self._on_open() - self._ws.run_forever() - - -# vim:sw=4:ts=4:et: diff --git a/platypush/backend/todoist/manifest.yaml b/platypush/backend/todoist/manifest.yaml deleted file mode 100644 index 6f556d8259..0000000000 --- a/platypush/backend/todoist/manifest.yaml +++ /dev/null @@ -1,16 +0,0 @@ -manifest: - events: - platypush.message.event.todoist.CheckedItemEvent: when an item is checked. - platypush.message.event.todoist.ItemContentChangeEvent: when the content of an - item is changed. - platypush.message.event.todoist.ModifiedItemEvent: when an item is changed and - the changedoesn't fall into the categories above. - platypush.message.event.todoist.NewItemEvent: when a new item is created. - platypush.message.event.todoist.RemovedItemEvent: when an item is removed. - platypush.message.event.todoist.TodoistSyncRequiredEvent: when an update has occurred - that doesn'tfall into the categories above and a sync is required to get up-to-date. - install: - pip: - - todoist-python - package: platypush.backend.todoist - type: backend diff --git a/platypush/message/event/todoist.py b/platypush/message/event/todoist.py deleted file mode 100644 index dc77fa3d52..0000000000 --- a/platypush/message/event/todoist.py +++ /dev/null @@ -1,59 +0,0 @@ -from platypush.message.event import Event - - -class TodoistEvent(Event): - pass - - -class NewItemEvent(TodoistEvent): - """ - Event triggered when a new item is created. - """ - - def __init__(self, item, *args, **kwargs): - super().__init__(*args, item=item, **kwargs) - - -class RemovedItemEvent(TodoistEvent): - """ - Event triggered when a new item is removed. - """ - - def __init__(self, item, *args, **kwargs): - super().__init__(*args, item=item, **kwargs) - - -class ModifiedItemEvent(TodoistEvent): - """ - Event triggered when an item is changed. - """ - - def __init__(self, item, *args, **kwargs): - super().__init__(*args, item=item, **kwargs) - - -class CheckedItemEvent(ModifiedItemEvent): - """ - Event triggered when an item is checked. - """ - - def __init__(self, item, *args, **kwargs): - super().__init__(*args, item=item, **kwargs) - - -class ItemContentChangeEvent(ModifiedItemEvent): - """ - Event triggered when the content of an item changes. - """ - - def __init__(self, item, *args, **kwargs): - super().__init__(*args, item=item, **kwargs) - - -class TodoistSyncRequiredEvent(TodoistEvent): - """ - Event triggered when an event occurs that doesn't fall into the categories above. - """ - - -# vim:sw=4:ts=4:et: diff --git a/platypush/message/response/todoist.py b/platypush/message/response/todoist.py deleted file mode 100644 index b4def1096b..0000000000 --- a/platypush/message/response/todoist.py +++ /dev/null @@ -1,326 +0,0 @@ -import todoist.models - -import datetime - -from typing import Optional, List, Dict, Any - -from platypush.message import Mapping -from platypush.message.response import Response - - -class TodoistResponse(Response): - pass - - -class TodoistUserResponse(TodoistResponse): - def __init__(self, - auto_reminder: Optional[int] = None, - avatar_big: Optional[str] = None, - avatar_medium: Optional[str] = None, - avatar_s640: Optional[str] = None, - avatar_small: Optional[str] = None, - business_account_id: Optional[int] = None, - daily_goal: Optional[int] = None, - date_format: Optional[str] = None, - dateist_inline_disabled: Optional[bool] = None, - dateist_lang: Optional[str] = None, - days_off: Optional[List[int]] = None, - default_reminder: Optional[str] = None, - email: Optional[str] = None, - features: Optional[Dict[str, Any]] = None, - full_name: Optional[str] = None, - id: Optional[int] = None, - image_id: Optional[str] = None, - inbox_project: Optional[int] = None, - is_biz_admin: Optional[bool] = None, - is_premium: Optional[bool] = None, - join_date: Optional[datetime.datetime] = None, - karma: Optional[float] = None, - karma_trend: Optional[str] = None, - lang: Optional[str] = None, - legacy_inbox_project: Optional[int] = None, - mobile_host: Optional[str] = None, - mobile_number: Optional[str] = None, - next_week: Optional[int] = None, - premium_until: Optional[datetime.datetime] = None, - share_limit: Optional[int] = None, - sort_order: Optional[int] = None, - start_day: Optional[int] = None, - start_page: Optional[str] = None, - theme: Optional[int] = None, - time_format: Optional[int] = None, - token: Optional[str] = None, - tz_info: Optional[Dict[str, Any]] = None, - unique_prefix: Optional[int] = None, - websocket_url: Optional[str] = None, - weekly_goal: Optional[int] = None, - **kwargs): - response = { - 'auto_reminder': auto_reminder, - 'avatar_big': avatar_big, - 'avatar_medium': avatar_medium, - 'avatar_s640': avatar_s640, - 'avatar_small': avatar_small, - 'business_account_id': business_account_id, - 'daily_goal': daily_goal, - 'date_format': date_format, - 'dateist_inline_disabled': dateist_inline_disabled, - 'dateist_lang': dateist_lang, - 'days_off': days_off, - 'default_reminder': default_reminder, - 'email': email, - 'features': features, - 'full_name': full_name, - 'id': id, - 'image_id': image_id, - 'inbox_project': inbox_project, - 'is_biz_admin': is_biz_admin, - 'is_premium': is_premium, - 'join_date': join_date, - 'karma': karma, - 'karma_trend': karma_trend, - 'lang': lang, - 'legacy_inbox_project': legacy_inbox_project, - 'mobile_host': mobile_host, - 'mobile_number': mobile_number, - 'next_week': next_week, - 'premium_until': premium_until, - 'share_limit': share_limit, - 'sort_order': sort_order, - 'start_day': start_day, - 'start_page': start_page, - 'theme': theme, - 'time_format': time_format, - 'token': token, - 'tz_info': tz_info, - 'unique_prefix': unique_prefix, - 'websocket_url': websocket_url, - 'weekly_goal': weekly_goal, - } - - super().__init__(output=response, **kwargs) - - -class TodoistProject(Mapping): - def __init__(self, - child_order: int, - collapsed: int, - color: int, - has_more_notes: bool, - id: int, - is_archived: bool, - is_deleted: bool, - is_favorite: bool, - name: str, - shared: bool, - inbox_project: Optional[bool] = None, - legacy_id: Optional[int] = None, - parent_id: Optional[int] = None, - *args, **kwargs): - super().__init__(*args, **kwargs) - - self.child_order = child_order - self.collapsed = collapsed - self.color = color - self.has_more_notes = has_more_notes - self.id = id - self.inbox_project = inbox_project - self.is_archived = bool(is_archived) - self.is_deleted = bool(is_deleted) - self.is_favorite = bool(is_favorite) - self.name = name - self.shared = shared - self.legacy_id = legacy_id - self.parent_id = parent_id - - -class TodoistProjectsResponse(TodoistResponse): - def __init__(self, projects: List[TodoistProject], **kwargs): - self.projects = [TodoistProject(**(p.data if isinstance(p, todoist.models.Project) else p)) for p in projects] - super().__init__(output=[p.__dict__ for p in self.projects], **kwargs) - - -class TodoistItem(Mapping): - def __init__(self, - content: str, - id: int, - checked: bool, - priority: int, - child_order: int, - collapsed: bool, - day_order: int, - date_added: datetime.datetime, - in_history: bool, - is_deleted: bool, - user_id: int, - has_more_notes: bool = False, - project_id: Optional[int] = None, - parent_id: Optional[int] = None, - responsible_uid: Optional[int] = None, - date_completed: Optional[datetime.datetime] = None, - assigned_by_uid: Optional[int] = None, - due: Optional[Dict[str, Any]] = None, - labels: Optional[List[str]] = None, - legacy_project_id: Optional[int] = None, - section_id: Optional[int] = None, - sync_id: Optional[int] = None, - *args, **kwargs): - super().__init__(*args, **kwargs) - - self.content = content - self.id = id - self.checked = bool(checked) - self.priority = priority - self.child_order = child_order - self.collapsed = bool(collapsed) - self.day_order = day_order - self.date_added = date_added - self.has_more_notes = bool(has_more_notes) - self.in_history = bool(in_history) - self.is_deleted = bool(is_deleted) - self.user_id = user_id - self.project_id = project_id - self.parent_id = parent_id - self.responsible_uid = responsible_uid - self.date_completed = date_completed - self.assigned_by_uid = assigned_by_uid - self.due = due - self.labels = labels - self.legacy_project_id = legacy_project_id - self.section_id = section_id - self.sync_id = sync_id - - -class TodoistItemsResponse(TodoistResponse): - def __init__(self, items: List[TodoistItem], **kwargs): - self.items = [TodoistItem(**(i.data if isinstance(i, todoist.models.Item) else i.__dict__)) for i in items] - super().__init__(output=[i.__dict__ for i in self.items], **kwargs) - - -class TodoistFilter(Mapping): - def __init__(self, - color: int, - id: int, - is_deleted: bool, - is_favorite: bool, - item_order: int, - name: str, - query: str, - legacy_id: Optional[int] = None, - *args, **kwargs): - super().__init__(*args, **kwargs) - - self.color = color - self.id = id - self.is_deleted = is_deleted - self.is_favorite = is_favorite - self.item_order = item_order - self.name = name - self.query = query - self.legacy_id = legacy_id - - -class TodoistFiltersResponse(TodoistResponse): - def __init__(self, filters: List[TodoistFilter], **kwargs): - self.filters = [TodoistFilter(**(f.data if isinstance(f, todoist.models.Filter) else f.__dict__)) - for f in filters] - - super().__init__(output=[f.__dict__ for f in self.filters], **kwargs) - - -class TodoistLiveNotification(Mapping): - def __init__(self, - id: int, - is_deleted: bool, - created: str, - is_unread: bool, - notification_key: str, - notification_type: str, - completed_last_month: Optional[int] = None, - karma_level: Optional[int] = None, - promo_img: Optional[str] = None, - completed_tasks: Optional[int] = None, - *args, **kwargs): - super().__init__(*args, **kwargs) - - self.id = id - self.is_deleted = bool(is_deleted) - self.completed_last_month = completed_last_month - self.completed_tasks = completed_tasks - self.created = created - self.is_unread = bool(is_unread) - self.karma_level = karma_level - self.notification_key = notification_key - self.notification_type = notification_type - self.promo_img = promo_img - - -class TodoistLiveNotificationsResponse(TodoistResponse): - def __init__(self, notifications: List[TodoistLiveNotification], **kwargs): - self.notifications = [TodoistLiveNotification(**(n.data if isinstance(n, todoist.models.LiveNotification) - else n.__dict__)) for n in notifications] - - super().__init__(output=[n.__dict__ for n in self.notifications], **kwargs) - - -class TodoistCollaborator(Mapping): - def __init__(self, data: Dict[str, Any], *args, **kwargs): - super().__init__(*args, **kwargs) - for k, v in data.items(): - self.__setattr__(k, v) - - -class TodoistCollaboratorsResponse(TodoistResponse): - def __init__(self, collaborators: List[TodoistCollaborator], **kwargs): - self.collaborators = [TodoistCollaborator(c.data if isinstance(c, todoist.models.Collaborator) else c.__dict__) - for c in collaborators] - - super().__init__(output=[c.__dict__ for c in self.collaborators], **kwargs) - - -class TodoistNote(Mapping): - def __init__(self, data: Dict[str, Any], *args, **kwargs): - super().__init__(*args, **kwargs) - for k, v in data.items(): - self.__setattr__(k, v) - - -class TodoistNotesResponse(TodoistResponse): - def __init__(self, notes: List[TodoistCollaborator], **kwargs): - self.notes = [TodoistCollaborator(n.data if isinstance(n, todoist.models.Note) else n.__dict__) - for n in notes] - - super().__init__(output=[n.__dict__ for n in self.notes], **kwargs) - - -class TodoistProjectNote(Mapping): - def __init__(self, data: Dict[str, Any], *args, **kwargs): - super().__init__(*args, **kwargs) - for k, v in data.items(): - self.__setattr__(k, v) - - -class TodoistProjectNotesResponse(TodoistResponse): - def __init__(self, notes: List[TodoistCollaborator], **kwargs): - self.notes = [TodoistCollaborator(n.data if isinstance(n, todoist.models.ProjectNote) else n.__dict__) - for n in notes] - - super().__init__(output=[n.__dict__ for n in self.notes], **kwargs) - - -class TodoistReminder(Mapping): - def __init__(self, data: Dict[str, Any], *args, **kwargs): - super().__init__(*args, **kwargs) - for k, v in data.items(): - self.__setattr__(k, v) - - -class TodoistRemindersResponse(TodoistResponse): - def __init__(self, reminders: List[TodoistReminder], **kwargs): - self.reminders = [TodoistReminder(n.data if isinstance(n, todoist.models.Reminder) else n.__dict__) - for n in reminders] - - super().__init__(output=[r.__dict__ for r in self.reminders], **kwargs) - - -# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/todoist/__init__.py b/platypush/plugins/todoist/__init__.py index 73210da4f1..0f19234c67 100644 --- a/platypush/plugins/todoist/__init__.py +++ b/platypush/plugins/todoist/__init__.py @@ -1,20 +1,20 @@ import time - from typing import Optional import todoist -import todoist.managers.items +import todoist.managers.items # type: ignore +import websocket from platypush.plugins import Plugin, action -from platypush.message.response.todoist import ( - TodoistUserResponse, - TodoistProjectsResponse, - TodoistItemsResponse, - TodoistFiltersResponse, - TodoistLiveNotificationsResponse, - TodoistCollaboratorsResponse, - TodoistNotesResponse, - TodoistProjectNotesResponse, +from platypush.schemas.todoist import ( + TodoistCollaboratorSchema, + TodoistFilterSchema, + TodoistItemSchema, + TodoistLiveNotificationSchema, + TodoistNoteSchema, + TodoistProjectNoteSchema, + TodoistProjectSchema, + TodoistUserSchema, ) @@ -22,14 +22,16 @@ class TodoistPlugin(Plugin): """ Todoist integration. - You'll also need a Todoist token. You can get it `here `. + You'll also need a Todoist token. You can get it `here + `_. """ _sync_timeout = 60.0 def __init__(self, api_token: str, **kwargs): """ - :param api_token: Todoist API token. You can get it `here `. + :param api_token: Todoist API token. You can get it `here + `_. """ super().__init__(**kwargs) @@ -37,9 +39,17 @@ class TodoistPlugin(Plugin): self._api = None self._last_sync_time = None - def _get_api(self) -> todoist.TodoistAPI: + self._ws_url: Optional[str] = None + self._ws: Optional[websocket.WebSocketApp] = None + self._connected = False + self._todoist_initialized = False + + self._items = {} + self._event_handled = False + + def _get_api(self) -> todoist.TodoistAPI: # type: ignore if not self._api: - self._api = todoist.TodoistAPI(self.api_token) + self._api = todoist.TodoistAPI(self.api_token) # type: ignore if ( not self._last_sync_time @@ -50,68 +60,82 @@ class TodoistPlugin(Plugin): return self._api @action - def get_user(self) -> TodoistUserResponse: + def get_user(self): """ Get logged user info. + + :return: .. schema:: todoist.TodoistUserSchema """ - api = self._get_api() - return TodoistUserResponse(**api.state['user']) + return TodoistUserSchema().dump(self._get_api().state['user']) @action - def get_projects(self) -> TodoistProjectsResponse: + def get_projects(self): """ Get list of Todoist projects. + + :return: .. schema:: todoist.TodoistProjectSchema(many=True) """ - api = self._get_api() - return TodoistProjectsResponse(api.state['projects']) + return TodoistProjectSchema().dump(self._get_api().state['projects'], many=True) @action - def get_items(self) -> TodoistItemsResponse: + def get_items(self): """ Get list of Todoist projects. + + :return .. schema:: todoist.TodoistItemSchema(many=True) """ - api = self._get_api() - return TodoistItemsResponse(api.state['items']) + return TodoistItemSchema().dump(self._get_api().state['items'], many=True) @action - def get_filters(self) -> TodoistFiltersResponse: + def get_filters(self): """ Get list of Todoist filters. + + :return: .. schema:: todoist.TodoistFilterSchema(many=True) """ - api = self._get_api() - return TodoistFiltersResponse(api.state['filters']) + return TodoistFilterSchema().dump(self._get_api().state['filters'], many=True) @action - def get_live_notifications(self) -> TodoistLiveNotificationsResponse: + def get_live_notifications(self): """ Get list of Todoist live notifications. + + :return: .. schema:: todoist.TodoistLiveNotificationSchema(many=True) """ - api = self._get_api() - return TodoistLiveNotificationsResponse(api.state['live_notifications']) + return TodoistLiveNotificationSchema().dump( + self._get_api().state['live_notifications'], many=True + ) @action - def get_collaborators(self) -> TodoistCollaboratorsResponse: + def get_collaborators(self): """ Get list of collaborators. + + :return: .. schema:: todoist.TodoistCollaboratorSchema(many=True) """ - api = self._get_api() - return TodoistCollaboratorsResponse(api.state['collaborators']) + return TodoistCollaboratorSchema().dump( + self._get_api().state['collaborators'], many=True + ) @action - def get_notes(self) -> TodoistNotesResponse: + def get_notes(self): """ Get list of Todoist notes. + + :return: .. schema:: todoist.TodoistNoteSchema(many=True) """ - api = self._get_api() - return TodoistNotesResponse(api.state['notes']) + return TodoistNoteSchema().dump(self._get_api().state['notes'], many=True) @action - def get_project_notes(self) -> TodoistProjectNotesResponse: + def get_project_notes(self): """ Get list of Todoist project notes. + + :return: .. schema:: todoist.TodoistProjectNoteSchema(many=True) """ - api = self._get_api() - return TodoistProjectNotesResponse(api.state['project_notes']) + return TodoistProjectNoteSchema().dump( + self._get_api().state['project_notes'], many=True + ) @action def add_item(self, content: str, project_id: Optional[int] = None, **kwargs): diff --git a/platypush/schemas/todoist.py b/platypush/schemas/todoist.py new file mode 100644 index 0000000000..41f8cb01e8 --- /dev/null +++ b/platypush/schemas/todoist.py @@ -0,0 +1,305 @@ +from marshmallow import EXCLUDE, fields +from marshmallow.schema import Schema + +from platypush.schemas import DateTime + + +class TodoistUserSchema(Schema): + """ + Todoist user schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + auto_reminder = fields.Int() + avatar_big = fields.Url( + metadata={ + "description": "The user's avatar URL.", + "example": "https://example.com/user/100x100.png", + }, + ) + + avatar_medium = fields.Url( + metadata={ + "description": "The user's avatar URL.", + "example": "https://example.com/user/50x50.png", + }, + ) + + avatar_s640 = fields.Url( + metadata={ + "description": "The user's avatar URL.", + "example": "https://example.com/user/640x640.png", + }, + ) + + avatar_small = fields.Url( + metadata={ + "description": "The user's avatar URL.", + "example": "https://example.com/user/25x25.png", + }, + ) + + business_account_id = fields.Int( + metadata={ + "description": "The user's business account ID.", + "example": 123456, + }, + ) + + daily_goal = fields.Int( + metadata={ + "description": "The user's daily goal.", + "example": 100, + }, + ) + + date_format = fields.String( + metadata={ + "description": "The user's date format.", + "example": "dd-mm-yyyy", + }, + ) + + dateist_inline_disabled = fields.Bool() + dateist_lang = fields.String( + metadata={ + "description": "The user's dateist language.", + "example": "en", + }, + ) + + days_off = fields.List( + fields.Int(), + metadata={ + "description": "The user's days off.", + "example": [0, 6], + }, + ) + + default_reminder = fields.String() + email = fields.Email( + metadata={ + "description": "The user's email.", + "example": "user@example.com", + }, + ) + + features = fields.Dict() + full_name = fields.String( + metadata={ + "description": "The user's full name.", + "example": "John Doe", + }, + ) + + id = fields.Int( + required=True, + metadata={ + "description": "The user's unique identifier.", + "example": 123456, + }, + ) + + image_id = fields.String() + inbox_project = fields.Int( + metadata={ + "description": "The user's inbox project ID.", + "example": 123456, + }, + ) + + is_biz_admin = fields.Bool() + is_premium = fields.Bool() + join_date = DateTime() + karma = fields.Float() + karma_trend = fields.String() + lang = fields.String( + metadata={ + "description": "The user's language.", + "example": "en", + }, + ) + + legacy_inbox_project = fields.Int() + mobile_host = fields.String() + mobile_number = fields.String() + next_week = fields.Int() + premium_until = DateTime() + share_limit = fields.Int() + sort_order = fields.Int() + start_day = fields.Int() + start_page = fields.String() + theme = fields.Int() + time_format = fields.String( + metadata={ + "description": "The user's time format.", + "example": "24h", + }, + ) + + token = fields.String() + tz_info = fields.Dict() + unique_prefix = fields.Int() + websocket_url = fields.Url() + weekly_goal = fields.Int() + + +class TodoistProjectSchema(Schema): + """ + Todoist project schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + child_order = fields.Int() + collapsed = fields.Bool() + color = fields.Int() + has_more_notes = fields.Bool() + id = fields.Int(required=True) + is_archived = fields.Bool() + is_deleted = fields.Bool() + is_favorite = fields.Bool() + name = fields.String(required=True) + shared = fields.Bool() + inbox_project = fields.Bool() + legacy_id = fields.Int() + parent_id = fields.Int() + + +class TodoistItemSchema(Schema): + """ + Todoist item schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + assigned_by_uid = fields.Int() + checked = fields.Bool() + child_order = fields.Int() + collapsed = fields.Bool() + content = fields.String() + date_added = DateTime() + date_completed = DateTime() + day_order = fields.Int() + due = fields.Dict() + has_more_notes = fields.Bool() + id = fields.Int(required=True) + in_history = fields.Bool() + is_deleted = fields.Bool() + labels = fields.List(fields.String()) + legacy_project_id = fields.Int() + parent_id = fields.Int() + priority = fields.Int() + project_id = fields.Int() + responsible_uid = fields.Int() + section_id = fields.Int() + sync_id = fields.Int() + user_id = fields.Int() + + +class TodoistFilterSchema(Schema): + """ + Todoist filter schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + color = fields.Int() + id = fields.Int(required=True) + is_deleted = fields.Bool() + item_order = fields.Int() + name = fields.String() + query = fields.String() + user_id = fields.Int() + + +class TodoistLiveNotificationSchema(Schema): + """ + Todoist live notifications schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + id = fields.Int(required=True) + notification_key = fields.String() + notification_type = fields.String() + project_id = fields.Int() + user_id = fields.Int() + is_deleted = fields.Bool() + is_unread = fields.Bool() + completed_last_month = fields.Bool() + karma_level = fields.Int() + promo_img = fields.Url() + completed_tasks = fields.Int() + created = fields.String() + + +class TodoistCollaboratorSchema(Schema): + """ + Todoist collaborator schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + data = fields.Dict() + + +class TodoistNoteSchema(Schema): + """ + Todoist note schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + data = fields.Dict() + + +class TodoistProjectNoteSchema(Schema): + """ + Todoist project note schema. + """ + + class Meta: # pylint: disable=too-few-public-methods + """ + Meta class. + """ + + unknown = EXCLUDE + + data = fields.Dict()