Added Trello integration

This commit is contained in:
Fabio Manganiello 2019-12-27 15:55:56 +01:00
parent f0999c7375
commit 5ba18ea7d5
8 changed files with 1126 additions and 13 deletions

View File

@ -230,6 +230,7 @@ autodoc_mock_imports = ['googlesamples.assistant.grpc.audio_helpers',
'avs', 'avs',
'PyOBEX', 'PyOBEX',
'todoist', 'todoist',
'trello',
] ]
sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../..'))

View File

@ -10,7 +10,6 @@ import logging
import os import os
import sys import sys
from .bus import Bus
from .bus.redis import RedisBus from .bus.redis import RedisBus
from .config import Config from .config import Config
from .context import register_backends from .context import register_backends
@ -164,12 +163,9 @@ class Daemon:
print('---- Starting platypush v.{}'.format(__version__)) print('---- Starting platypush v.{}'.format(__version__))
redis_conf = Config.get('backend.redis') redis_conf = Config.get('backend.redis', {})
if redis_conf: self.bus = RedisBus(on_message=self.on_message(),
self.bus = RedisBus(on_message=self.on_message(), **redis_conf.get('redis_args', {}))
**redis_conf.get('redis_args', {}))
else:
self.bus = Bus(on_message=self.on_message())
# Initialize the backends and link them to the bus # Initialize the backends and link them to the bus
self.backends = register_backends(bus=self.bus, global_scope=True) self.backends = register_backends(bus=self.bus, global_scope=True)

View File

@ -1,3 +1,4 @@
import datetime
import logging import logging
import inspect import inspect
import json import json
@ -23,7 +24,7 @@ class Message(object):
for attr in self.__dir__() for attr in self.__dir__()
if (attr != '_timestamp' or not attr.startswith('_')) if (attr != '_timestamp' or not attr.startswith('_'))
and not inspect.ismethod(getattr(self, attr)) and not inspect.ismethod(getattr(self, attr))
}).replace('\n', ' ') }, cls=MessageEncoder).replace('\n', ' ')
def __bytes__(self): def __bytes__(self):
""" """
@ -77,6 +78,8 @@ class Message(object):
class Mapping(dict): class Mapping(dict):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
for k, v in kwargs.items():
self.__setattr__(k, v)
def __setitem__(self, key, item): def __setitem__(self, key, item):
self.__dict__[key] = item self.__dict__[key] = item
@ -130,4 +133,14 @@ class Mapping(dict):
return str(self.__dict__) return str(self.__dict__)
class MessageEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime) or \
isinstance(obj, datetime.date) or \
isinstance(obj, datetime.time):
return obj.isoformat()
return super().default(obj)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View File

@ -1,7 +1,7 @@
import json import json
import time import time
from platypush.message import Message from platypush.message import Message, MessageEncoder
class Response(Message): class Response(Message):
@ -67,6 +67,10 @@ class Response(Message):
the message into a UTF-8 JSON string the message into a UTF-8 JSON string
""" """
output = self.output if self.output is not None and self.output != {} else {
'status': 'ok' if not self.errors else 'error'
}
response_dict = { response_dict = {
'id': self.id, 'id': self.id,
'type': 'response', 'type': 'response',
@ -74,7 +78,7 @@ class Response(Message):
'origin': self.origin if hasattr(self, 'origin') else None, 'origin': self.origin if hasattr(self, 'origin') else None,
'_timestamp': self.timestamp, '_timestamp': self.timestamp,
'response': { 'response': {
'output': self.output, 'output': output,
'errors': self.errors, 'errors': self.errors,
}, },
} }
@ -82,7 +86,7 @@ class Response(Message):
if self.disable_logging: if self.disable_logging:
response_dict['_disable_logging'] = self.disable_logging response_dict['_disable_logging'] = self.disable_logging
return json.dumps(response_dict) return json.dumps(response_dict, cls=MessageEncoder)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View File

@ -0,0 +1,185 @@
import datetime
from typing import Optional, List, Union
from platypush.message import Mapping
from platypush.message.response import Response
class TrelloResponse(Response):
pass
class TrelloList(Mapping):
def __init__(self,
id: str,
name: str,
closed: bool,
subscribed: bool,
*args, **kwargs):
super().__init__(id=id, name=name, closed=closed, subscribed=subscribed, *args, **kwargs)
class TrelloBoard(Mapping):
def __init__(self,
id: str,
name: str,
url: str,
closed: bool,
lists: Optional[List[TrelloList]] = None,
description: Optional[str] = None,
date_last_activity: Optional[datetime.datetime] = None,
*args, **kwargs):
super().__init__(id=id, name=name, url=url, closed=closed, description=description, lists=lists,
date_last_activity=date_last_activity, *args, **kwargs)
class TrelloBoardResponse(TrelloResponse):
def __init__(self, board: TrelloBoard, **kwargs):
super().__init__(output=board, **kwargs)
class TrelloBoardsResponse(TrelloResponse):
def __init__(self, boards: List[TrelloBoard], **kwargs):
super().__init__(output=boards, **kwargs)
class TrelloListsResponse(TrelloResponse):
def __init__(self, lists: List[TrelloList], **kwargs):
super().__init__(output=lists, **kwargs)
class TrelloLabel(Mapping):
def __init__(self,
id: str,
name: str,
color: Optional[str] = None,
*args, **kwargs):
super().__init__(id=id, name=name, color=color, *args, **kwargs)
class TrelloUser(Mapping):
def __init__(self,
id: str,
username: str,
fullname: str,
initials: Optional[str] = None,
avatar_url: Optional[str] = None,
*args, **kwargs):
super().__init__(id=id, username=username, fullname=fullname, initials=initials,
avatar_url=avatar_url, *args, **kwargs)
class TrelloComment(Mapping):
# noinspection PyShadowingBuiltins
def __init__(self,
id: str,
text: str,
type: str,
creator: TrelloUser,
date: Union[str, datetime.datetime],
*args, **kwargs):
super().__init__(id=id, text=text, type=type, creator=creator, date=date, *args, **kwargs)
class TrelloCard(Mapping):
# noinspection PyShadowingBuiltins
def __init__(self,
id: str,
name: str,
url: str,
closed: bool,
board: TrelloBoard,
is_due_complete: bool,
list: Optional[TrelloList] = None,
comments: Optional[List[TrelloComment]] = None,
labels: Optional[List[TrelloLabel]] = None,
description: Optional[str] = None,
due_date: Optional[Union[datetime.datetime, str]] = None,
latest_card_move_date: Optional[Union[datetime.datetime, str]] = None,
date_last_activity: Optional[Union[datetime.datetime, str]] = None,
*args, **kwargs):
super().__init__(id=id, name=name, url=url, closed=closed, board=board, is_due_complete=is_due_complete,
description=description, date_last_activity=date_last_activity, due_date=due_date, list=list,
comments=comments, labels=labels, latest_card_move_date=latest_card_move_date, *args, **kwargs)
class TrelloCardResponse(TrelloResponse):
def __init__(self, card: TrelloCard, **kwargs):
super().__init__(output=card, **kwargs)
class TrelloCardsResponse(TrelloResponse):
def __init__(self, cards: List[TrelloCard], **kwargs):
super().__init__(output=cards, **kwargs)
class TrelloPreview(Mapping):
# noinspection PyShadowingBuiltins
def __init__(self,
id: str,
scaled: bool,
url: str,
bytes: int,
height: int,
width: int,
*args, **kwargs):
super().__init__(id=id, scaled=scaled, url=url, bytes=bytes, height=height, width=width, *args, **kwargs)
class TrelloAttachment(Mapping):
# noinspection PyShadowingBuiltins
def __init__(self,
id: str,
bytes: int,
date: str,
edge_color: str,
id_member: str,
is_upload: bool,
name: str,
previews: List[TrelloPreview],
url: str,
mime_type: Optional[str] = None,
*args, **kwargs):
super().__init__(id=id, bytes=bytes, date=date, edge_color=edge_color, id_member=id_member, is_upload=is_upload,
name=name, previews=previews, url=url, mime_type=mime_type, *args, **kwargs)
class TrelloChecklistItem(Mapping):
def __init__(self,
id: str,
name: str,
checked: bool,
*args, **kwargs):
super().__init__(id=id, name=name, checked=checked, *args, **kwargs)
class TrelloChecklist(Mapping):
def __init__(self,
id: str,
name: str,
checklist_items: List[TrelloChecklistItem],
*args, **kwargs):
super().__init__(id=id, name=name, checklist_items=checklist_items, *args, **kwargs)
class TrelloMember(Mapping):
def __init__(self,
id: str,
full_name: str,
bio: Optional[str],
url: Optional[str],
username: Optional[str],
initials: Optional[str],
member_type: Optional[str] = None,
*args, **kwargs):
super().__init__(id=id, full_name=full_name, bio=bio, url=url, username=username, initials=initials,
member_type=member_type, *args, **kwargs)
class TrelloMembersResponse(Mapping):
def __init__(self, members: List[TrelloMember], **kwargs):
super().__init__(output=members, **kwargs)
# vim:sw=4:ts=4:et:

908
platypush/plugins/trello.py Normal file
View File

@ -0,0 +1,908 @@
import datetime
from typing import Optional, Dict, List, Union
# noinspection PyPackageRequirements
import trello
# noinspection PyPackageRequirements
from trello.board import Board, List as List_
# noinspection PyPackageRequirements
from trello.exceptions import ResourceUnavailable
from platypush.message.response.trello import TrelloBoard, TrelloBoardsResponse, TrelloCardsResponse, TrelloCard, \
TrelloAttachment, TrelloPreview, TrelloChecklist, TrelloChecklistItem, TrelloUser, TrelloComment, TrelloLabel, \
TrelloList, TrelloBoardResponse, TrelloListsResponse, TrelloMembersResponse, TrelloMember, TrelloCardResponse
from platypush.plugins import Plugin, action
class TrelloPlugin(Plugin):
"""
Trello integration.
Requires:
* **py-trello** (``pip install py-trello``)
You'll also need a Trello API key. You can get it `here <https://trello.com/app-key>`.
You'll also need an auth token if you want to view/change private resources. You can generate a permanent token
linked to your account on
https://trello.com/1/connect?key=<KEY>&name=platypush&response_type=token&expiration=never&scope=read,write
"""
def __init__(self, api_key: str, api_secret: Optional[str] = None, token: Optional[str] = None, **kwargs):
"""
:param api_key: Trello API key. You can get it `here <https://trello.com/app-key>`.
:param api_secret: Trello API secret. You can get it `here <https://trello.com/app-key>`.
:param token: Trello token. It is required if you want to access or modify private resources. You can get
a permanent token on
https://trello.com/1/connect?key=<KEY>&name=platypush&response_type=token&expiration=never&scope=read,write
"""
super().__init__(**kwargs)
self.api_key = api_key
self.api_secret = api_secret
self.token = token
self._client = None
def _get_client(self) -> trello.TrelloClient:
if not self._client:
self._client = trello.TrelloClient(
api_key=self.api_key,
api_secret=self.api_secret,
token=self.token,
)
return self._client
def _get_board(self, board: str) -> Board:
client = self._get_client()
try:
return client.get_board(board)
except ResourceUnavailable:
boards = [b for b in client.list_boards() if b.name == board]
assert boards, 'No such board: {}'.format(board)
return boards[0]
# noinspection PyShadowingBuiltins
@action
def get_boards(self, all: bool = False) -> TrelloBoardsResponse:
"""
Get the list of boards.
:param all: If True, return all the boards included those that have been closed/archived/deleted. Otherwise,
only return open/active boards (default: False).
"""
client = self._get_client()
return TrelloBoardsResponse([
TrelloBoard(
id=b.id,
name=b.name,
description=b.description,
url=b.url,
date_last_activity=b.date_last_activity,
closed=b.closed,
)
for b in client.list_boards(board_filter='all' if all else 'open')
])
@action
def get_board(self, board: str) -> TrelloBoardResponse:
"""
Get the info about a board.
:param board: Board ID or name
"""
board = self._get_board(board)
return TrelloBoardResponse(
TrelloBoard(
id=board.id,
name=board.name,
url=board.url,
closed=board.closed,
description=board.description,
date_last_activity=board.date_last_activity,
lists=[
TrelloList(id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed)
for ll in board.list_lists()
]
)
)
@action
def open_board(self, board: str):
"""
Re-open/un-archive a board.
:param board: Board ID or name
"""
board = self._get_board(board)
board.open()
@action
def close_board(self, board: str):
"""
Close/archive a board.
:param board: Board ID or name
"""
board = self._get_board(board)
board.close()
@action
def set_board_name(self, board: str, name: str):
"""
Change the name of a board.
:param board: Board ID or name.
:param name: New name.
"""
board = self._get_board(board)
board.set_name(name)
return self.get_board(name)
@action
def set_board_description(self, board: str, description: str):
"""
Change the description of a board.
:param board: Board ID or name.
:param description: New description.
"""
board = self._get_board(board)
board.set_description(description)
return self.get_board(description)
@action
def create_label(self, board: str, name: str, color: Optional[str] = None):
"""
Add a label to a board.
:param board: Board ID or name
:param name: Label name
:param color: Optional HTML color
"""
board = self._get_board(board)
board.add_label(name=name, color=color)
@action
def delete_label(self, board: str, label: str):
"""
Delete a label from a board.
:param board: Board ID or name
:param label: Label ID or name
"""
board = self._get_board(board)
try:
board.delete_label(label)
except ResourceUnavailable:
labels = [
ll for ll in board.get_labels()
if ll.name == label
]
assert labels, 'No such label: {}'.format(label)
label = labels[0].id
board.delete_label(label)
@action
def add_member(self, board: str, member_id: str, member_type: str = 'normal'):
"""
Add a member to a board.
:param board: Board ID or name.
:param member_id: Member ID to add.
:param member_type: Member type - can be 'normal' or 'admin' (default: 'normal').
"""
board = self._get_board(board)
board.add_member(member_id, member_type=member_type)
@action
def remove_member(self, board: str, member_id: str):
"""
Remove a member from a board.
:param board: Board ID or name.
:param member_id: Member ID to remove.
"""
board = self._get_board(board)
board.remove_member(member_id)
def _get_members(self, board: str, only_admin: bool = False) -> TrelloMembersResponse:
board = self._get_board(board)
members = board.admin_members() if only_admin else board.get_members()
return TrelloMembersResponse([
TrelloMember(
id=m.id,
full_name=m.full_name,
bio=m.bio,
url=m.url,
username=m.username,
initials=m.initials,
member_type=getattr(m, 'member_type') if hasattr(m, 'member_type') else None
)
for m in members
])
@action
def get_members(self, board: str) -> TrelloMembersResponse:
"""
Get the list of all the members of a board.
:param board: Board ID or name.
"""
return self._get_members(board, only_admin=False)
@action
def get_admin_members(self, board: str) -> TrelloMembersResponse:
"""
Get the list of the admin members of a board.
:param board: Board ID or name.
"""
return self._get_members(board, only_admin=True)
# noinspection PyShadowingBuiltins
@action
def get_lists(self, board: str, all: bool = False) -> TrelloListsResponse:
"""
Get the list of lists on a board.
:param board: Board ID or name
:param all: If True, return all the lists, included those that have been closed/archived/deleted. Otherwise,
only return open/active lists (default: False).
"""
board = self._get_board(board)
return TrelloListsResponse([
TrelloList(id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed)
for ll in board.list_lists('all' if all else 'open')
])
@action
def add_list(self, board: str, name: str, pos: Optional[int] = None):
"""
Add a list to a board.
:param board: Board ID or name
:param name: List name
:param pos: Optional position (default: last)
"""
board = self._get_board(board)
board.add_list(name=name, pos=pos)
# noinspection PyShadowingBuiltins
def _get_list(self, board: str, list: str) -> List_:
board = self._get_board(board)
try:
return board.get_list(list)
except ResourceUnavailable:
lists = [ll for ll in board.list_lists() if ll.name == list]
assert lists, 'No such list: {}'.format(list)
return lists[0]
# noinspection PyShadowingBuiltins
@action
def set_list_name(self, board: str, list: str, name: str):
"""
Change the name of a board list.
:param board: Board ID or name
:param list: List ID or name
:param name: New name
"""
list = self._get_list(board, list)
list.set_name(name)
# noinspection PyShadowingBuiltins
@action
def list_subscribe(self, board: str, list: str):
"""
Subscribe to a list.
:param board: Board ID or name
:param list: List ID or name
"""
list = self._get_list(board, list)
list.subscribe()
# noinspection PyShadowingBuiltins
@action
def list_unsubscribe(self, board: str, list: str):
"""
Unsubscribe from a list.
:param board: Board ID or name
:param list: List ID or name
"""
list = self._get_list(board, list)
list.unsubscribe()
# noinspection PyShadowingBuiltins
@action
def archive_all_cards(self, board: str, list: str):
"""
Archive all the cards on a list.
:param board: Board ID or name
:param list: List ID or name
"""
list = self._get_list(board, list)
list.archive_all_cards()
@action
def move_all_cards(self, board: str, src: str, dest: str):
"""
Move all the cards from a list to another.
:param board: Board ID or name
:param src: Source list
:param dest: Target list
"""
src = self._get_list(board, src)
dest = self._get_list(board, dest)
src.move_all_cards(dest.id)
# noinspection PyShadowingBuiltins
@action
def open_list(self, board: str, list: str):
"""
Open/un-archive a list.
:param board: Board ID or name
:param list: List ID or name
"""
list = self._get_list(board, list)
list.open()
# noinspection PyShadowingBuiltins
@action
def close_list(self, board: str, list: str):
"""
Close/archive a list.
:param board: Board ID or name
:param list: List ID or name
"""
list = self._get_list(board, list)
list.close()
# noinspection PyShadowingBuiltins
@action
def move_list(self, board: str, list: str, position: int):
"""
Move a list to another position.
:param board: Board ID or name
:param list: List ID or name
:param position: New position index
"""
list = self._get_list(board, list)
list.move(position)
# noinspection PyShadowingBuiltins
@action
def add_card(self, board: str, list: str, name: str, description: Optional[str] = None,
position: Optional[int] = None, labels: Optional[List[str]] = None,
due: Optional[Union[str, datetime.datetime]] = None, source: Optional[str] = None,
assign: Optional[List[str]] = None) -> TrelloCardResponse:
"""
Add a card to a list.
:param board: Board ID or name
:param list: List ID or name
:param name: Card name
:param description: Card description
:param position: Card position index
:param labels: List of labels
:param due: Due date (``datetime.datetime`` object or ISO-format string)
:param source: Card ID to clone from
:param assign: List of assignee member IDs
"""
list = self._get_list(board, list)
if labels:
labels = [
ll for ll in list.board.get_labels()
if ll.id in labels or ll.name in labels
]
card = list.add_card(name=name, desc=description, labels=labels, due=due, source=source, position=position,
assign=assign)
return TrelloCardResponse(TrelloCard(id=card.id,
name=card.name,
url=card.url,
closed=card.closed,
board=TrelloBoard(
id=list.board.id,
name=list.board.name,
url=list.board.url,
closed=list.board.closed,
description=list.board.description,
date_last_activity=list.board.date_last_activity
),
is_due_complete=card.is_due_complete,
list=None,
comments=[],
labels=[
TrelloLabel(
id=lb.id,
name=lb.name,
color=lb.color
)
for lb in (card.labels or [])
],
description=card.description,
due_date=card.due_date,
latest_card_move_date=card.latestCardMove_date,
date_last_activity=card.date_last_activity
))
@action
def delete_card(self, card_id: str):
"""
Permanently delete a card.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.delete()
@action
def open_card(self, card_id: str):
"""
Open/un-archive a card.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.set_closed(False)
@action
def close_card(self, card_id: str):
"""
Close/archive a card.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.set_closed(True)
@action
def add_checklist(self, card_id: str, title: str, items: List[str], states: Optional[List[bool]] = None):
"""
Add a checklist to a card.
:param card_id: Card ID
:param title: Checklist title
:param items: List of items in the checklist
:param states: State of each item, True for checked, False for unchecked
"""
client = self._get_client()
card = client.get_card(card_id)
card.add_checklist(title, items, states)
@action
def add_label(self, card_id: str, label: str):
"""
Add a label to a card.
:param card_id: Card ID
:param label: Label name
"""
client = self._get_client()
card = client.get_card(card_id)
labels = [
ll for ll in card.board.get_labels()
if ll.name == label
]
assert labels, 'No such label: {}'.format(label)
label = labels[0]
card.add_label(label)
@action
def remove_label(self, card_id: str, label: str):
"""
Remove a label from a card.
:param card_id: Card ID
:param label: Label name
"""
client = self._get_client()
card = client.get_card(card_id)
labels = [
ll for ll in card.board.get_labels()
if ll.name == label
]
assert labels, 'No such label: {}'.format(label)
label = labels[0]
card.remove_label(label)
@action
def assign_card(self, card_id: str, member_id: str):
"""
Assign a card.
:param card_id: Card ID
:param member_id: Member ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.assign(member_id)
@action
def unassign_card(self, card_id: str, member_id: str):
"""
Un-assign a card.
:param card_id: Card ID
:param member_id: Member ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.unassign(member_id)
@action
def attach_card(self, card_id: str, name: Optional[str] = None, mime_type: Optional[str] = None,
file: Optional[str] = None, url: Optional[str] = None):
"""
Add an attachment to a card. It can be either a local file or a remote URL.
:param card_id: Card ID
:param name: File name
:param mime_type: MIME type
:param file: Path to the file
:param url: URL to the file
"""
client = self._get_client()
card = client.get_card(card_id)
card.attach(name=name, mimeType=mime_type, file=file, url=url)
@action
def remove_attachment(self, card_id: str, attachment_id: str):
"""
Remove an attachment from a card.
:param card_id: Card ID
:param attachment_id: Attachment ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.remove_attachment(attachment_id)
# noinspection PyShadowingBuiltins
@action
def change_card_board(self, card_id: str, board: str, list: str = None):
"""
Move a card to a new board.
:param card_id: Card ID
:param board: New board ID or name
:param list: Optional target list ID or name
"""
client = self._get_client()
card = client.get_card(card_id)
board = self._get_board(board)
list_id = None
if list:
list_id = self._get_list(board.id, list).id
card.change_board(board.id, list_id)
# noinspection PyShadowingBuiltins
@action
def change_card_list(self, card_id: str, list: str):
"""
Move a card to a new list.
:param card_id: Card ID
:param list: List ID or name
"""
client = self._get_client()
card = client.get_card(card_id)
list = self._get_list(card.board.id, list)
card.change_list(list.id)
@action
def change_card_pos(self, card_id: str, position: int):
"""
Move a card to a new position.
:param card_id: Card ID
:param position: New position index
"""
client = self._get_client()
card = client.get_card(card_id)
card.change_pos(position)
@action
def comment_card(self, card_id: str, text: str):
"""
Add a comment to a card.
:param card_id: Card ID
:param text: Comment text
"""
client = self._get_client()
card = client.get_card(card_id)
card.comment(text)
@action
def update_comment(self, card_id: str, comment_id: str, text: str):
"""
Update the content of a comment.
:param card_id: Card ID
:param comment_id: Comment ID
:param text: New comment text
"""
client = self._get_client()
card = client.get_card(card_id)
card.update_comment(comment_id, text)
@action
def delete_comment(self, card_id: str, comment_id: str):
"""
Delete a comment.
:param card_id: Card ID
:param comment_id: Comment ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.delete_comment(comment_id)
@action
def set_card_name(self, card_id: str, name: str):
"""
Change the name of a card.
:param card_id: Card ID
:param name: New name
"""
client = self._get_client()
card = client.get_card(card_id)
card.set_name(name)
@action
def set_card_description(self, card_id: str, description: str):
"""
Change the description of a card.
:param card_id: Card ID
:param description: New description
"""
client = self._get_client()
card = client.get_card(card_id)
card.set_description(description)
@action
def add_card_member(self, card_id: str, member_id: str):
"""
Add a member to a card.
:param card_id: Card ID
:param member_id: Member ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.add_member(member_id)
@action
def remove_card_member(self, card_id: str, member_id: str):
"""
Remove a member from a card.
:param card_id: Card ID
:param member_id: Member ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.remove_member(member_id)
@action
def set_card_due(self, card_id: str, due: Union[str, datetime.datetime]):
"""
Set the due date for a card.
:param card_id: Card ID
:param due: Due date, as a datetime.datetime object or an ISO string
"""
client = self._get_client()
card = client.get_card(card_id)
if isinstance(due, str):
due = datetime.datetime.fromisoformat(due)
card.set_due(due)
@action
def remove_card_due(self, card_id: str):
"""
Remove the due date from a card.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.remove_due()
@action
def set_card_due_complete(self, card_id: str):
"""
Set the due date of a card as completed.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.set_due_complete()
@action
def remove_card_due_complete(self, card_id: str):
"""
Remove the due complete flag from a card.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.remove_due_complete()
@action
def card_subscribe(self, card_id: str):
"""
Subscribe to a card.
:param card_id: Card ID
"""
client = self._get_client()
card = client.get_card(card_id)
card.subscribe()
# noinspection PyShadowingBuiltins
@action
def get_cards(self, board: str, list: Optional[str] = None, all: bool = False) -> TrelloCardsResponse:
"""
Get the list of cards on a board.
:param board: Board ID or name
:param list: List ID or name. If set then the method will only return the cards found on that list
(default: None)
:param all: If True, return all the cards included those that have been closed/archived/deleted. Otherwise,
only return open/active cards (default: False).
"""
board = self._get_board(board)
lists: Dict[str, TrelloList] = {
ll.id: TrelloList(id=ll.id, name=ll.name, closed=ll.closed, subscribed=ll.subscribed)
for ll in board.list_lists()
}
list_id = None
if list:
if list in lists:
list_id = list
else:
# noinspection PyUnresolvedReferences
ll = [l1 for l1 in lists.values() if l1.name == list]
assert ll, 'No such list ID/name: {}'.format(list)
# noinspection PyUnresolvedReferences
list_id = ll[0].id
# noinspection PyUnresolvedReferences
return TrelloCardsResponse([
TrelloCard(
id=c.id,
name=c.name,
url=c.url,
closed=c.closed,
list=lists.get(c.list_id),
board=TrelloBoard(
id=c.board.id,
name=c.board.name,
url=c.board.url,
closed=c.board.closed,
description=c.board.description,
date_last_activity=c.board.date_last_activity
),
attachments=[
TrelloAttachment(
id=a.get('id'),
bytes=a.get('bytes'),
date=a.get('date'),
edge_color=a.get('edgeColor'),
id_member=a.get('idMember'),
is_upload=a.get('isUpload'),
name=a.get('name'),
previews=[
TrelloPreview(
id=p.get('id'),
scaled=p.get('scaled'),
url=p.get('url'),
bytes=p.get('bytes'),
height=p.get('height'),
width=p.get('width')
)
for p in a.get('previews', [])
],
url=a.get('url'),
mime_type=a.get('mimeType')
)
for a in c.attachments
],
checklists=[
TrelloChecklist(
id=ch.id,
name=ch.name,
checklist_items=[
TrelloChecklistItem(
id=i.get('id'),
name=i.get('name'),
checked=i.get('checked')
)
for i in ch.items
]
)
for ch in c.checklists
],
comments=[
TrelloComment(
id=co.get('id'),
text=co.get('data', {}).get('text'),
type=co.get('type'),
date=co.get('date'),
creator=TrelloUser(
id=co.get('memberCreator', {}).get('id'),
username=co.get('memberCreator', {}).get('username'),
fullname=co.get('memberCreator', {}).get('fullName'),
initials=co.get('memberCreator', {}).get('initials'),
avatar_url=co.get('memberCreator', {}).get('avatarUrl')
)
)
for co in c.comments
],
labels=[
TrelloLabel(
id=lb.id,
name=lb.name,
color=lb.color
)
for lb in (c.labels or [])
],
is_due_complete=c.is_due_complete,
due_date=c.due_date,
description=c.description,
latest_card_move_date=c.latestCardMove_date,
date_last_activity=c.date_last_activity
)
for c in board.all_cards() if (
(all or not c.closed) and
(not list or c.list_id == list_id)
)
])
# vim:sw=4:ts=4:et:

View File

@ -190,4 +190,8 @@ croniter
# nodered # nodered
# Support for Todoist integration # Support for Todoist integration
# todoist # todoist-python
# Support for Trello integration
# py-trello

View File

@ -253,7 +253,9 @@ setup(
# Support for Node-RED integration # Support for Node-RED integration
'nodered': ['pynodered'], 'nodered': ['pynodered'],
# Support for Todoist integration # Support for Todoist integration
'nodered': ['todoist-python'], 'todoist': ['todoist-python'],
# Support for Trello integration
'trello': ['py-trello'],
}, },
) )