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, Optional, Protocol 7 8from libs.types import ActionStatus, ChannelType, CommandType, MessageStatus 9 10if TYPE_CHECKING: 11 from pathlib import Path # noqa: F401 12 13 import pandas as pd # noqa: F401 14 15 from libs.types import MessageType, StyleOptions 16 17 18class DataMixin: 19 """共通処理""" 20 21 def reset(self) -> None: 22 """デフォルト値にリセット""" 23 if not is_dataclass(self): 24 raise TypeError(f"{self.__class__.__name__} must be a dataclass") 25 26 default = type(self)() 27 for f in fields(self): 28 setattr(self, f.name, getattr(default, f.name)) 29 30 31@dataclass 32class MsgData(DataMixin): 33 """ポストされたメッセージデータ""" 34 35 text: str = field(default=str()) 36 """本文""" 37 event_ts: str = field(default="undetermined") 38 """イベント発生タイムスタンプ""" 39 thread_ts: str = field(default="undetermined") 40 """スレッド元タイムスタンプ 41 - *0*: スレッドになっていない 42 - *undetermined*: 未定義状態 43 """ 44 edited_ts: str = field(default="undetermined") 45 """イベント編集タイムスタンプ""" 46 channel_id: str = field(default=str()) 47 """チャンネルID""" 48 channel_type: ChannelType = field(default=ChannelType.UNDETERMINED) 49 """チャンネルタイプ""" 50 user_id: str = field(default=str()) 51 """ユーザーID""" 52 status: MessageStatus = field(default=MessageStatus.UNDETERMINED) 53 """イベントステータス""" 54 reaction_ok: list[str] = field(default_factory=list) 55 reaction_ng: list[str] = field(default_factory=list) 56 remarks: list[str] = field(default_factory=list) 57 """メモ格納用""" 58 59 60@dataclass 61class PostData(DataMixin): 62 """ポストするデータ""" 63 64 headline: Optional[tuple["MessageType", "StyleOptions"]] = field(default=None) 65 """ヘッダメッセージ""" 66 message: list[tuple["MessageType", "StyleOptions"]] = field(default_factory=list) 67 """本文メッセージ""" 68 thread: bool = field(default=True) 69 """スレッドに返す""" 70 ts: str = field(default="undetermined") 71 """指定タイムスタンプへの強制リプライ""" 72 thread_title: str = field(default="") 73 """スレッドに付けるタイトル""" 74 75 76@dataclass 77class StatusData(DataMixin): 78 """処理した結果""" 79 80 command_type: CommandType = field(default=CommandType.UNKNOWN) 81 """実行(する/した)サブコマンド""" 82 command_flg: bool = field(default=False) 83 """コマンドとして実行されたかチェック 84 - *True*: コマンド実行 85 - *False*: キーワード呼び出し 86 """ 87 command_name: str = field(default="") 88 """実行したコマンド名""" 89 90 reaction: bool = field(default=False) 91 """データステータス状態 92 - *True*: 矛盾なくデータを取り込んだ(OK) 93 - *False*: 矛盾があったがデータを取り込んだ or データを取り込めなかった(NG) 94 """ 95 action: ActionStatus = field(default=ActionStatus.NOTHING) 96 """DBに対する操作""" 97 target_ts: list[str] = field(default_factory=list) 98 """同じ処理をしたタイムスタンプリスト(1件だけの処理でもセットされる)""" 99 rpoint_sum: int = field(default=0) 100 """素点合計値格納用""" 101 102 result: bool = field(default=True) 103 """メッセージデータに対する処理結果 104 - *True*: 目的の処理が達成できた 105 - *False*: 何らかの原因で処理が達成できなかった 106 """ 107 message: Any = field(default=None) 108 """汎用メッセージ""" 109 source: str = field(default="") 110 """データ入力元識別子""" 111 112 113class MessageParserProtocol(Protocol): 114 """メッセージ解析クラス""" 115 116 data: MsgData 117 """受け取ったメッセージデータ""" 118 post: PostData 119 """送信する内容""" 120 status: StatusData 121 """処理した結果""" 122 123 @property 124 def in_thread(self) -> bool: 125 """スレッド内のメッセージか判定""" 126 127 @property 128 def is_command(self) -> bool: 129 """ 130 コマンドとして実行されたかチェック 131 132 Returns: 133 bool: 真偽値 134 - *True*: スラッシュコマンド 135 - *False*: チャンネル内呼び出しキーワード 136 137 """ 138 139 @property 140 def is_bot(self) -> bool: 141 """ 142 botによる操作かチェック 143 144 Returns: 145 bool: 真偽値 146 - *True*: botが操作 147 - *False*: ユーザが操作 148 149 """ 150 151 @property 152 def keyword(self) -> str: 153 """コマンドとして認識している文字列を返す""" 154 155 @property 156 def argument(self) -> list[str]: 157 """コマンド引数として認識しているオプションを文字列のリストで返す""" 158 159 @property 160 def reply_ts(self) -> str: 161 """リプライ先のタイムスタンプ""" 162 163 @property 164 def check_updatable(self) -> bool: 165 """DB更新可能チャンネルか判定""" 166 167 @property 168 def ignore_user(self) -> bool: 169 """コマンドを拒否するユーザか判定""" 170 171 def set_headline(self, data: "MessageType", options: "StyleOptions") -> None: 172 """ヘッドラインメッセージをセット""" 173 174 def set_message(self, data: "MessageType", options: "StyleOptions") -> None: 175 """本文メッセージをセット""" 176 177 def parser(self, body: Any) -> None: 178 """メッセージ解析メソッド""" 179 180 def reset(self) -> None: 181 """状態リセット"""
class
DataMixin:
19class DataMixin: 20 """共通処理""" 21 22 def reset(self) -> None: 23 """デフォルト値にリセット""" 24 if not is_dataclass(self): 25 raise TypeError(f"{self.__class__.__name__} must be a dataclass") 26 27 default = type(self)() 28 for f in fields(self): 29 setattr(self, f.name, getattr(default, f.name))
共通処理
32@dataclass 33class MsgData(DataMixin): 34 """ポストされたメッセージデータ""" 35 36 text: str = field(default=str()) 37 """本文""" 38 event_ts: str = field(default="undetermined") 39 """イベント発生タイムスタンプ""" 40 thread_ts: str = field(default="undetermined") 41 """スレッド元タイムスタンプ 42 - *0*: スレッドになっていない 43 - *undetermined*: 未定義状態 44 """ 45 edited_ts: str = field(default="undetermined") 46 """イベント編集タイムスタンプ""" 47 channel_id: str = field(default=str()) 48 """チャンネルID""" 49 channel_type: ChannelType = field(default=ChannelType.UNDETERMINED) 50 """チャンネルタイプ""" 51 user_id: str = field(default=str()) 52 """ユーザーID""" 53 status: MessageStatus = field(default=MessageStatus.UNDETERMINED) 54 """イベントステータス""" 55 reaction_ok: list[str] = field(default_factory=list) 56 reaction_ng: list[str] = field(default_factory=list) 57 remarks: list[str] = field(default_factory=list) 58 """メモ格納用"""
ポストされたメッセージデータ
MsgData( text: str = '', event_ts: str = 'undetermined', thread_ts: str = 'undetermined', edited_ts: str = 'undetermined', channel_id: str = '', channel_type: libs.types.ChannelType = <ChannelType.UNDETERMINED: 'undetermined'>, user_id: str = '', status: libs.types.MessageStatus = <MessageStatus.UNDETERMINED: 'undetermined'>, reaction_ok: list[str] = <factory>, reaction_ng: list[str] = <factory>, remarks: list[str] = <factory>)
61@dataclass 62class PostData(DataMixin): 63 """ポストするデータ""" 64 65 headline: Optional[tuple["MessageType", "StyleOptions"]] = field(default=None) 66 """ヘッダメッセージ""" 67 message: list[tuple["MessageType", "StyleOptions"]] = field(default_factory=list) 68 """本文メッセージ""" 69 thread: bool = field(default=True) 70 """スレッドに返す""" 71 ts: str = field(default="undetermined") 72 """指定タイムスタンプへの強制リプライ""" 73 thread_title: str = field(default="") 74 """スレッドに付けるタイトル"""
ポストするデータ
PostData( headline: tuple[None | str | pathlib.Path | pandas.DataFrame, libs.types.StyleOptions] | None = None, message: list[tuple[None | str | pathlib.Path | pandas.DataFrame, libs.types.StyleOptions]] = <factory>, thread: bool = True, ts: str = 'undetermined', thread_title: str = '')
headline: tuple[None | str | pathlib.Path | pandas.DataFrame, libs.types.StyleOptions] | None =
None
ヘッダメッセージ
77@dataclass 78class StatusData(DataMixin): 79 """処理した結果""" 80 81 command_type: CommandType = field(default=CommandType.UNKNOWN) 82 """実行(する/した)サブコマンド""" 83 command_flg: bool = field(default=False) 84 """コマンドとして実行されたかチェック 85 - *True*: コマンド実行 86 - *False*: キーワード呼び出し 87 """ 88 command_name: str = field(default="") 89 """実行したコマンド名""" 90 91 reaction: bool = field(default=False) 92 """データステータス状態 93 - *True*: 矛盾なくデータを取り込んだ(OK) 94 - *False*: 矛盾があったがデータを取り込んだ or データを取り込めなかった(NG) 95 """ 96 action: ActionStatus = field(default=ActionStatus.NOTHING) 97 """DBに対する操作""" 98 target_ts: list[str] = field(default_factory=list) 99 """同じ処理をしたタイムスタンプリスト(1件だけの処理でもセットされる)""" 100 rpoint_sum: int = field(default=0) 101 """素点合計値格納用""" 102 103 result: bool = field(default=True) 104 """メッセージデータに対する処理結果 105 - *True*: 目的の処理が達成できた 106 - *False*: 何らかの原因で処理が達成できなかった 107 """ 108 message: Any = field(default=None) 109 """汎用メッセージ""" 110 source: str = field(default="") 111 """データ入力元識別子"""
処理した結果
StatusData( command_type: libs.types.CommandType = <CommandType.UNKNOWN: 'unknown'>, command_flg: bool = False, command_name: str = '', reaction: bool = False, action: libs.types.ActionStatus = <ActionStatus.NOTHING: 'nothing'>, target_ts: list[str] = <factory>, rpoint_sum: int = 0, result: bool = True, message: Any = None, source: str = '')
class
MessageParserProtocol(typing.Protocol):
114class MessageParserProtocol(Protocol): 115 """メッセージ解析クラス""" 116 117 data: MsgData 118 """受け取ったメッセージデータ""" 119 post: PostData 120 """送信する内容""" 121 status: StatusData 122 """処理した結果""" 123 124 @property 125 def in_thread(self) -> bool: 126 """スレッド内のメッセージか判定""" 127 128 @property 129 def is_command(self) -> bool: 130 """ 131 コマンドとして実行されたかチェック 132 133 Returns: 134 bool: 真偽値 135 - *True*: スラッシュコマンド 136 - *False*: チャンネル内呼び出しキーワード 137 138 """ 139 140 @property 141 def is_bot(self) -> bool: 142 """ 143 botによる操作かチェック 144 145 Returns: 146 bool: 真偽値 147 - *True*: botが操作 148 - *False*: ユーザが操作 149 150 """ 151 152 @property 153 def keyword(self) -> str: 154 """コマンドとして認識している文字列を返す""" 155 156 @property 157 def argument(self) -> list[str]: 158 """コマンド引数として認識しているオプションを文字列のリストで返す""" 159 160 @property 161 def reply_ts(self) -> str: 162 """リプライ先のタイムスタンプ""" 163 164 @property 165 def check_updatable(self) -> bool: 166 """DB更新可能チャンネルか判定""" 167 168 @property 169 def ignore_user(self) -> bool: 170 """コマンドを拒否するユーザか判定""" 171 172 def set_headline(self, data: "MessageType", options: "StyleOptions") -> None: 173 """ヘッドラインメッセージをセット""" 174 175 def set_message(self, data: "MessageType", options: "StyleOptions") -> None: 176 """本文メッセージをセット""" 177 178 def parser(self, body: Any) -> None: 179 """メッセージ解析メソッド""" 180 181 def reset(self) -> None: 182 """状態リセット"""
メッセージ解析クラス
MessageParserProtocol(*args, **kwargs)
1866def _no_init_or_replace_init(self, *args, **kwargs): 1867 cls = type(self) 1868 1869 if cls._is_protocol: 1870 raise TypeError('Protocols cannot be instantiated') 1871 1872 # Already using a custom `__init__`. No need to calculate correct 1873 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1874 if cls.__init__ is not _no_init_or_replace_init: 1875 return 1876 1877 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1878 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1879 # searches for a proper new `__init__` in the MRO. The new `__init__` 1880 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1881 # instantiation of the protocol subclass will thus use the new 1882 # `__init__` and no longer call `_no_init_or_replace_init`. 1883 for base in cls.__mro__: 1884 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1885 if init is not _no_init_or_replace_init: 1886 cls.__init__ = init 1887 break 1888 else: 1889 # should not happen 1890 cls.__init__ = object.__init__ 1891 1892 cls.__init__(self, *args, **kwargs)
is_command: bool
128 @property 129 def is_command(self) -> bool: 130 """ 131 コマンドとして実行されたかチェック 132 133 Returns: 134 bool: 真偽値 135 - *True*: スラッシュコマンド 136 - *False*: チャンネル内呼び出しキーワード 137 138 """
コマンドとして実行されたかチェック
Returns:
bool: 真偽値
- True: スラッシュコマンド
- False: チャンネル内呼び出しキーワード
is_bot: bool
140 @property 141 def is_bot(self) -> bool: 142 """ 143 botによる操作かチェック 144 145 Returns: 146 bool: 真偽値 147 - *True*: botが操作 148 - *False*: ユーザが操作 149 150 """
botによる操作かチェック
Returns:
bool: 真偽値
- True: botが操作
- False: ユーザが操作
def
set_headline( self, data: None | str | pathlib.Path | pandas.DataFrame, options: libs.types.StyleOptions) -> None:
172 def set_headline(self, data: "MessageType", options: "StyleOptions") -> None: 173 """ヘッドラインメッセージをセット"""
ヘッドラインメッセージをセット
def
set_message( self, data: None | str | pathlib.Path | pandas.DataFrame, options: libs.types.StyleOptions) -> None:
175 def set_message(self, data: "MessageType", options: "StyleOptions") -> None: 176 """本文メッセージをセット"""
本文メッセージをセット