codekingpro/portable-devtools
114k
1from __future__ import annotations2 3from typing import TYPE_CHECKING, Any, Iterator, Literal, Optional, Tuple4 5from langchain_core.documents import Document6from langchain_core.utils import get_from_env7 8from langchain_community.document_loaders.base import BaseLoader9 10if TYPE_CHECKING:11 from trello import Board, Card, TrelloClient12 13 14class TrelloLoader(BaseLoader):15 """Load cards from a `Trello` board."""16 17 def __init__(18 self,19 client: TrelloClient,20 board_name: str,21 *,22 include_card_name: bool = True,23 include_comments: bool = True,24 include_checklist: bool = True,25 card_filter: Literal["closed", "open", "all"] = "all",26 extra_metadata: Tuple[str, ...] = ("due_date", "labels", "list", "closed"),27 ):28 """Initialize Trello loader.29 30 Args:31 client: Trello API client.32 board_name: The name of the Trello board.33 include_card_name: Whether to include the name of the card in the document.34 include_comments: Whether to include the comments on the card in the35 document.36 include_checklist: Whether to include the checklist on the card in the37 document.38 card_filter: Filter on card status. Valid values are "closed", "open",39 "all".40 extra_metadata: List of additional metadata fields to include as document41 metadata.Valid values are "due_date", "labels", "list", "closed".42 43 """44 self.client = client45 self.board_name = board_name46 self.include_card_name = include_card_name47 self.include_comments = include_comments48 self.include_checklist = include_checklist49 self.extra_metadata = extra_metadata50 self.card_filter = card_filter51 52 @classmethod53 def from_credentials(54 cls,55 board_name: str,56 *,57 api_key: Optional[str] = None,58 token: Optional[str] = None,59 **kwargs: Any,60 ) -> TrelloLoader:61 """Convenience constructor that builds TrelloClient init param for you.62 63 Args:64 board_name: The name of the Trello board.65 api_key: Trello API key. Can also be specified as environment variable66 TRELLO_API_KEY.67 token: Trello token. Can also be specified as environment variable68 TRELLO_TOKEN.69 include_card_name: Whether to include the name of the card in the document.70 include_comments: Whether to include the comments on the card in the71 document.72 include_checklist: Whether to include the checklist on the card in the73 document.74 card_filter: Filter on card status. Valid values are "closed", "open",75 "all".76 extra_metadata: List of additional metadata fields to include as document77 metadata.Valid values are "due_date", "labels", "list", "closed".78 """79 80 try:81 from trello import TrelloClient82 except ImportError as ex:83 raise ImportError(84 "Could not import trello python package. "85 "Please install it with `pip install py-trello`."86 ) from ex87 api_key = api_key or get_from_env("api_key", "TRELLO_API_KEY")88 token = token or get_from_env("token", "TRELLO_TOKEN")89 client = TrelloClient(api_key=api_key, token=token)90 return cls(client, board_name, **kwargs)91 92 def lazy_load(self) -> Iterator[Document]:93 """Loads all cards from the specified Trello board.94 95 You can filter the cards, metadata and text included by using the optional96 parameters.97 98 Returns:99 A list of documents, one for each card in the board.100 """101 try:102 from bs4 import BeautifulSoup # noqa: F401103 except ImportError as ex:104 raise ImportError(105 "`beautifulsoup4` package not found, please run"106 " `pip install beautifulsoup4`"107 ) from ex108 109 board = self._get_board()110 # Create a dictionary with the list IDs as keys and the list names as values111 list_dict = {list_item.id: list_item.name for list_item in board.list_lists()}112 # Get Cards on the board113 cards = board.get_cards(card_filter=self.card_filter)114 for card in cards:115 yield self._card_to_doc(card, list_dict)116 117 def _get_board(self) -> Board:118 # Find the first board with a matching name119 board = next(120 (b for b in self.client.list_boards() if b.name == self.board_name), None121 )122 if not board:123 raise ValueError(f"Board `{self.board_name}` not found.")124 return board125 126 def _card_to_doc(self, card: Card, list_dict: dict) -> Document:127 from bs4 import BeautifulSoup128 129 text_content = ""130 if self.include_card_name:131 text_content = card.name + "\n"132 if card.description.strip():133 text_content += BeautifulSoup(card.description, "lxml").get_text()134 if self.include_checklist:135 # Get all the checklist items on the card136 for checklist in card.checklists:137 if checklist.items:138 items = [139 f"{item['name']}:{item['state']}" for item in checklist.items140 ]141 text_content += f"\n{checklist.name}\n" + "\n".join(items)142 143 if self.include_comments:144 # Get all the comments on the card145 comments = [146 BeautifulSoup(comment["data"]["text"], "lxml").get_text()147 for comment in card.comments148 ]149 text_content += "Comments:" + "\n".join(comments)150 151 # Default metadata fields152 metadata = {153 "title": card.name,154 "id": card.id,155 "url": card.url,156 }157 158 # Extra metadata fields. Card object is not subscriptable.159 if "labels" in self.extra_metadata:160 metadata["labels"] = [label.name for label in card.labels]161 if "list" in self.extra_metadata:162 if card.list_id in list_dict:163 metadata["list"] = list_dict[card.list_id]164 if "closed" in self.extra_metadata:165 metadata["closed"] = card.closed166 if "due_date" in self.extra_metadata:167 metadata["due_date"] = card.due_date168 169 return Document(page_content=text_content, metadata=metadata)170 