integrations.protocols
integrations/protocols.py
1""" 2integrations/protocols.py 3""" 4 5from dataclasses import dataclass, field, fields, is_dataclass 6from typing import TYPE_CHECKING, Any, Literal, Protocol 7 8if TYPE_CHECKING: 9 from libs.types import MessageType, MessageTypeDict, StyleOptions 10 11 12class DataMixin: 13 """共通処理""" 14 15 def reset(self) -> None: 16 """デフォルト値にリセット""" 17 if not is_dataclass(self): 18 raise TypeError(f"{self.__class__.__name__} must be a dataclass") 19 20 default = type(self)() 21 for f in fields(self): 22 setattr(self, f.name, getattr(default, f.name)) 23 24 25@dataclass 26class MsgData(DataMixin): 27 """ポストされたメッセージデータ""" 28 29 text: str = field(default=str()) 30 """本文""" 31 event_ts: str = field(default="undetermined") 32 """イベント発生タイムスタンプ""" 33 thread_ts: str = field(default="undetermined") 34 """スレッド元タイムスタンプ 35 - *0*: スレッドになっていない 36 - *undetermined*: 未定義状態 37 """ 38 edited_ts: str = field(default="undetermined") 39 """イベント編集タイムスタンプ""" 40 channel_id: str = field(default=str()) 41 """チャンネルID""" 42 channel_type: Literal[ 43 "channel", 44 "group", 45 "im", 46 "search_messages", 47 "undetermined", 48 ] = field(default="undetermined") 49 """チャンネルタイプ 50 - *channel*: 通常チャンネル 51 - *group*: プライベートチャンネル 52 - *im*: ダイレクトメッセージ 53 - *search_messages*: 検索API 54 - *undetermined*: 未定義状態 55 """ 56 user_id: str = field(default=str()) 57 """ユーザーID""" 58 status: Literal[ 59 "message_append", 60 "message_changed", 61 "message_deleted", 62 "undetermined", 63 ] = field(default="undetermined") 64 """イベントステータス 65 - *message_append*: 新規ポスト 66 - *message_changed*: 編集 67 - *message_deleted*: 削除 68 - *undetermined*: 未定義状態 69 """ 70 reaction_ok: list = field(default_factory=list) 71 reaction_ng: list = field(default_factory=list) 72 remarks: list = field(default_factory=list) 73 """メモ格納用""" 74 75 76@dataclass 77class PostData(DataMixin): 78 """ポストするデータ""" 79 80 headline: dict[str, str] = field(default_factory=dict) 81 """ヘッダ文""" 82 message: list[dict[str, "MessageTypeDict"]] = field(default_factory=list) 83 """本文 84 識別子(タイトルなど)をキーにした辞書型 85 """ 86 thread: bool = field(default=True) 87 """スレッドに返す""" 88 ts: str = field(default="undetermined") 89 """指定タイムスタンプへの強制リプライ""" 90 91 92@dataclass 93class StatusData(DataMixin): 94 """処理した結果""" 95 96 command_type: Literal[ 97 "results", 98 "graph", 99 "ranking", 100 "rating", 101 "report", 102 "comparison", 103 "unknown", 104 ] = field(default="unknown") 105 """実行(する/した)サブコマンド 106 - *results*: 成績サマリ 107 - *graph*: グラフ生成 108 - *ranking*: ランキング 109 - *rating*: レーティング 110 - *report*: レポート 111 - *comparison*: 突合処理 112 - *unknown*: 未定義 113 """ 114 115 command_flg: bool = field(default=False) 116 """コマンドとして実行されたかチェック 117 - *True*: コマンド実行 118 - *False*: キーワード呼び出し 119 """ 120 command_name: str = field(default="") 121 """実行したコマンド名""" 122 123 reaction: bool = field(default=False) 124 """データステータス状態 125 - *True*: 矛盾なくデータを取り込んだ(OK) 126 - *False*: 矛盾があったがデータを取り込んだ or データを取り込めなかった(NG) 127 """ 128 action: Literal["change", "delete", "nothing"] = field(default="nothing") 129 """DBに対する操作 130 - *change*: insert/updateが実行された 131 - *delete*: deleteが実行された 132 - *nothing*: 何もしてない 133 """ 134 target_ts: list = field(default_factory=list) 135 """同じ処理をしたタイムスタンプリスト(1件だけの処理でもセットされる)""" 136 rpoint_sum: int = field(default=0) 137 """素点合計値格納用""" 138 139 result: bool = field(default=True) 140 """メッセージデータに対する処理結果 141 - *True*: 目的の処理が達成できた 142 - *False*: 何らかの原因で処理が達成できなかった 143 """ 144 message: Any = field(default=None) 145 """汎用メッセージ""" 146 source: str = field(default="") 147 """入力元識別子""" 148 149 150class MessageParserProtocol(Protocol): 151 """メッセージ解析クラス""" 152 153 data: MsgData 154 """受け取ったメッセージデータ""" 155 post: PostData 156 """送信する内容""" 157 status: StatusData 158 """処理した結果""" 159 160 @property 161 def in_thread(self) -> bool: 162 """スレッド内のメッセージか判定""" 163 164 @property 165 def is_command(self) -> bool: 166 """コマンドとして実行されたかチェック 167 - *True*: スラッシュコマンド 168 - *False*: チャンネル内呼び出しキーワード 169 """ 170 171 @property 172 def is_bot(self) -> bool: 173 """botによる操作かチェック 174 - *True*: botが操作 175 - *False*: ユーザが操作 176 """ 177 178 @property 179 def keyword(self) -> str: 180 """コマンドとして認識している文字列を返す""" 181 182 @property 183 def argument(self) -> list: 184 """コマンド引数として認識しているオプションを文字列のリストで返す""" 185 186 @property 187 def reply_ts(self) -> str: 188 """リプライ先のタイムスタンプ""" 189 190 @property 191 def check_updatable(self) -> bool: 192 """DB更新可能チャンネルか判定""" 193 194 @property 195 def ignore_user(self) -> bool: 196 """コマンドを拒否するユーザか判定""" 197 198 def set_data(self, title: str, data: "MessageType", options: "StyleOptions"): 199 """メッセージデータをセット""" 200 201 def get_score(self, keyword: str) -> dict: 202 """本文からスコアデータを取り出す""" 203 204 def get_remarks(self, keyword: str) -> list: 205 """本文からメモデータを取り出す""" 206 207 def parser(self, body: Any): 208 """メッセージ解析メソッド""" 209 210 def reset(self): 211 """状態リセット"""
class
DataMixin:
13class DataMixin: 14 """共通処理""" 15 16 def reset(self) -> None: 17 """デフォルト値にリセット""" 18 if not is_dataclass(self): 19 raise TypeError(f"{self.__class__.__name__} must be a dataclass") 20 21 default = type(self)() 22 for f in fields(self): 23 setattr(self, f.name, getattr(default, f.name))
共通処理
26@dataclass 27class MsgData(DataMixin): 28 """ポストされたメッセージデータ""" 29 30 text: str = field(default=str()) 31 """本文""" 32 event_ts: str = field(default="undetermined") 33 """イベント発生タイムスタンプ""" 34 thread_ts: str = field(default="undetermined") 35 """スレッド元タイムスタンプ 36 - *0*: スレッドになっていない 37 - *undetermined*: 未定義状態 38 """ 39 edited_ts: str = field(default="undetermined") 40 """イベント編集タイムスタンプ""" 41 channel_id: str = field(default=str()) 42 """チャンネルID""" 43 channel_type: Literal[ 44 "channel", 45 "group", 46 "im", 47 "search_messages", 48 "undetermined", 49 ] = field(default="undetermined") 50 """チャンネルタイプ 51 - *channel*: 通常チャンネル 52 - *group*: プライベートチャンネル 53 - *im*: ダイレクトメッセージ 54 - *search_messages*: 検索API 55 - *undetermined*: 未定義状態 56 """ 57 user_id: str = field(default=str()) 58 """ユーザーID""" 59 status: Literal[ 60 "message_append", 61 "message_changed", 62 "message_deleted", 63 "undetermined", 64 ] = field(default="undetermined") 65 """イベントステータス 66 - *message_append*: 新規ポスト 67 - *message_changed*: 編集 68 - *message_deleted*: 削除 69 - *undetermined*: 未定義状態 70 """ 71 reaction_ok: list = field(default_factory=list) 72 reaction_ng: list = field(default_factory=list) 73 remarks: list = field(default_factory=list) 74 """メモ格納用"""
ポストされたメッセージデータ
MsgData( text: str = '', event_ts: str = 'undetermined', thread_ts: str = 'undetermined', edited_ts: str = 'undetermined', channel_id: str = '', channel_type: Literal['channel', 'group', 'im', 'search_messages', 'undetermined'] = 'undetermined', user_id: str = '', status: Literal['message_append', 'message_changed', 'message_deleted', 'undetermined'] = 'undetermined', reaction_ok: list = <factory>, reaction_ng: list = <factory>, remarks: list = <factory>)
channel_type: Literal['channel', 'group', 'im', 'search_messages', 'undetermined'] =
'undetermined'
チャンネルタイプ
- channel: 通常チャンネル
- group: プライベートチャンネル
- im: ダイレクトメッセージ
- search_messages: 検索API
- undetermined: 未定義状態
77@dataclass 78class PostData(DataMixin): 79 """ポストするデータ""" 80 81 headline: dict[str, str] = field(default_factory=dict) 82 """ヘッダ文""" 83 message: list[dict[str, "MessageTypeDict"]] = field(default_factory=list) 84 """本文 85 識別子(タイトルなど)をキーにした辞書型 86 """ 87 thread: bool = field(default=True) 88 """スレッドに返す""" 89 ts: str = field(default="undetermined") 90 """指定タイムスタンプへの強制リプライ"""
ポストするデータ
PostData( headline: dict[str, str] = <factory>, message: list[dict[str, libs.types.MessageTypeDict]] = <factory>, thread: bool = True, ts: str = 'undetermined')
93@dataclass 94class StatusData(DataMixin): 95 """処理した結果""" 96 97 command_type: Literal[ 98 "results", 99 "graph", 100 "ranking", 101 "rating", 102 "report", 103 "comparison", 104 "unknown", 105 ] = field(default="unknown") 106 """実行(する/した)サブコマンド 107 - *results*: 成績サマリ 108 - *graph*: グラフ生成 109 - *ranking*: ランキング 110 - *rating*: レーティング 111 - *report*: レポート 112 - *comparison*: 突合処理 113 - *unknown*: 未定義 114 """ 115 116 command_flg: bool = field(default=False) 117 """コマンドとして実行されたかチェック 118 - *True*: コマンド実行 119 - *False*: キーワード呼び出し 120 """ 121 command_name: str = field(default="") 122 """実行したコマンド名""" 123 124 reaction: bool = field(default=False) 125 """データステータス状態 126 - *True*: 矛盾なくデータを取り込んだ(OK) 127 - *False*: 矛盾があったがデータを取り込んだ or データを取り込めなかった(NG) 128 """ 129 action: Literal["change", "delete", "nothing"] = field(default="nothing") 130 """DBに対する操作 131 - *change*: insert/updateが実行された 132 - *delete*: deleteが実行された 133 - *nothing*: 何もしてない 134 """ 135 target_ts: list = field(default_factory=list) 136 """同じ処理をしたタイムスタンプリスト(1件だけの処理でもセットされる)""" 137 rpoint_sum: int = field(default=0) 138 """素点合計値格納用""" 139 140 result: bool = field(default=True) 141 """メッセージデータに対する処理結果 142 - *True*: 目的の処理が達成できた 143 - *False*: 何らかの原因で処理が達成できなかった 144 """ 145 message: Any = field(default=None) 146 """汎用メッセージ""" 147 source: str = field(default="") 148 """入力元識別子"""
処理した結果
StatusData( command_type: Literal['results', 'graph', 'ranking', 'rating', 'report', 'comparison', 'unknown'] = 'unknown', command_flg: bool = False, command_name: str = '', reaction: bool = False, action: Literal['change', 'delete', 'nothing'] = 'nothing', target_ts: list = <factory>, rpoint_sum: int = 0, result: bool = True, message: Any = None, source: str = '')
command_type: Literal['results', 'graph', 'ranking', 'rating', 'report', 'comparison', 'unknown'] =
'unknown'
実行(する/した)サブコマンド
- results: 成績サマリ
- graph: グラフ生成
- ranking: ランキング
- rating: レーティング
- report: レポート
- comparison: 突合処理
- unknown: 未定義
reaction: bool =
False
データステータス状態
- True: 矛盾なくデータを取り込んだ(OK)
- False: 矛盾があったがデータを取り込んだ or データを取り込めなかった(NG)
class
MessageParserProtocol(typing.Protocol):
151class MessageParserProtocol(Protocol): 152 """メッセージ解析クラス""" 153 154 data: MsgData 155 """受け取ったメッセージデータ""" 156 post: PostData 157 """送信する内容""" 158 status: StatusData 159 """処理した結果""" 160 161 @property 162 def in_thread(self) -> bool: 163 """スレッド内のメッセージか判定""" 164 165 @property 166 def is_command(self) -> bool: 167 """コマンドとして実行されたかチェック 168 - *True*: スラッシュコマンド 169 - *False*: チャンネル内呼び出しキーワード 170 """ 171 172 @property 173 def is_bot(self) -> bool: 174 """botによる操作かチェック 175 - *True*: botが操作 176 - *False*: ユーザが操作 177 """ 178 179 @property 180 def keyword(self) -> str: 181 """コマンドとして認識している文字列を返す""" 182 183 @property 184 def argument(self) -> list: 185 """コマンド引数として認識しているオプションを文字列のリストで返す""" 186 187 @property 188 def reply_ts(self) -> str: 189 """リプライ先のタイムスタンプ""" 190 191 @property 192 def check_updatable(self) -> bool: 193 """DB更新可能チャンネルか判定""" 194 195 @property 196 def ignore_user(self) -> bool: 197 """コマンドを拒否するユーザか判定""" 198 199 def set_data(self, title: str, data: "MessageType", options: "StyleOptions"): 200 """メッセージデータをセット""" 201 202 def get_score(self, keyword: str) -> dict: 203 """本文からスコアデータを取り出す""" 204 205 def get_remarks(self, keyword: str) -> list: 206 """本文からメモデータを取り出す""" 207 208 def parser(self, body: Any): 209 """メッセージ解析メソッド""" 210 211 def reset(self): 212 """状態リセット"""
メッセージ解析クラス
MessageParserProtocol(*args, **kwargs)
1771def _no_init_or_replace_init(self, *args, **kwargs): 1772 cls = type(self) 1773 1774 if cls._is_protocol: 1775 raise TypeError('Protocols cannot be instantiated') 1776 1777 # Already using a custom `__init__`. No need to calculate correct 1778 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1779 if cls.__init__ is not _no_init_or_replace_init: 1780 return 1781 1782 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1783 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1784 # searches for a proper new `__init__` in the MRO. The new `__init__` 1785 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1786 # instantiation of the protocol subclass will thus use the new 1787 # `__init__` and no longer call `_no_init_or_replace_init`. 1788 for base in cls.__mro__: 1789 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1790 if init is not _no_init_or_replace_init: 1791 cls.__init__ = init 1792 break 1793 else: 1794 # should not happen 1795 cls.__init__ = object.__init__ 1796 1797 cls.__init__(self, *args, **kwargs)
is_command: bool
165 @property 166 def is_command(self) -> bool: 167 """コマンドとして実行されたかチェック 168 - *True*: スラッシュコマンド 169 - *False*: チャンネル内呼び出しキーワード 170 """
コマンドとして実行されたかチェック
- True: スラッシュコマンド
- False: チャンネル内呼び出しキーワード
is_bot: bool
172 @property 173 def is_bot(self) -> bool: 174 """botによる操作かチェック 175 - *True*: botが操作 176 - *False*: ユーザが操作 177 """
botによる操作かチェック
- True: botが操作
- False: ユーザが操作
199 def set_data(self, title: str, data: "MessageType", options: "StyleOptions"): 200 """メッセージデータをセット"""
メッセージデータをセット