wolf4032's picture
Upload 6 files
ece09c3 verified
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Callable, Literal, Tuple
import gradio as gr
class GrComponent(ABC):
"""
コンポーネントのクラス
全てのコンポーネントを作成してから.render()できるようにするためのクラス
各コンポーネントに対して実行される処理のメソッドを子クラスに追加していく
Attributes
----------
comp: gr.Component
コンポーネント
"""
def __init__(self, *param: Any):
"""
コンストラクタ
createメソッドでコンポーネントを作成する
"""
self.comp: gr.Component = self._create(*param)
@abstractmethod
def _create(self, *param: Any) -> gr.Component:
"""
コンポーネントの作成
Returns
-------
gr.Component
作成されるコンポーネント
"""
pass
class GrLayout(ABC):
"""
レイアウトのクラス
gr.Rowや、gr.Columnを事前に定義しておくためのクラス
Attributes
----------
layout_type: gr.BlockContext, default None
gr.Rowやgr.Columnなどの、子要素の配置スタイル
children: (Dict[str, GrLayout | GrComponent] | List[GrLayout | GrComponent])
子要素
"""
layout_type: gr.BlockContext = None
def __init__(self, *param: Any):
"""
コンストラクタ
子クラス固有のレイアウトタイプにオーバーライドされているか確認し、
子要素をもつ辞書かリストを作成する
Raises
------
NotImplementedError
レイアウトタイプがNoneの場合
"""
if self.layout_type is None:
raise NotImplementedError('layout_typeを設定してください')
self.children: (
Dict[str, GrLayout | GrComponent] | List[GrLayout | GrComponent]
) = self._create(*param)
@abstractmethod
def _create(
self, *param: Any
) -> Dict[str, GrLayout | GrComponent] | List[GrLayout | GrComponent]:
"""
子要素をまとめた辞書かリストの作成
Returns
-------
Dict[str, GrLayout | GrComponent] | List[GrLayout | GrComponent]
子要素が入った辞書かリスト
"""
pass
class GrListener:
"""
イベントリスナーのクラス
GrBlocksで、まとめてイベントリスナーを設定できるように、
必要な項目を定義しておくクラス
本来、イベントリスナーはwith gr.Blocks():の中で定義するものだが、
withの外で作成し、後でまとめてブロックに適用することができる
"""
def __init__(
self,
trigger: Callable[[], Any] | List[Callable[[], Any]] = None,
fn: Callable | None | Literal["decorator"] = None,
inputs: GrComponent | list[GrComponent | GrLayout] | None = None,
outputs: GrComponent | list[GrComponent | GrLayout] | None = None,
api_name: str | None | Literal[False] = None,
scroll_to_output: bool = False,
show_progress: Literal["full", "minimal", "hidden"] = 'full',
queue: bool | None = None,
batch: bool = False,
max_batch_size: int = 4,
preprocess: bool = True,
postprocess: bool = True,
cancels: dict[str, Any] | list[dict[str, Any]] | None = None,
every: float | None = None,
trigger_mode: Literal["once", "multiple", "always_last"] | None = None,
js: str | None = None,
concurrency_limit: int | None | Literal["default"] = "default",
concurrency_id: str | None = None,
show_api: bool = True,
thens: GrListener | List[GrListener] | None = None
):
"""
コンストラクタ
引数についてはGradioのソースコードを参照してください
thenは、あるイベントリスナー実行直後に実行させる処理を指定するものです
詳細は、Gradioの公式サイトを参照してください(https://www.gradio.app/guides/blocks-and-event-listeners#running-events-consecutively)
triggerは、gr.onによる複数のトリガー指定に対応できるよう
List[Callable[[], Any]]に対応している
inputsとoutputsは、GrComponentのオブジェクトをそのまま指定するだけでよく、
.compにする必要はない
Raises
------
NotImplementedError
fnが設定されていない場合
"""
if fn is None:
raise NotImplementedError('fnを指定してください')
self._trigger = trigger
self.fn = fn
self.inputs = InputsOutputsMaker.setup_inputs_or_outputs(inputs)
self.outputs = InputsOutputsMaker.setup_inputs_or_outputs(outputs)
self._api_name = api_name
self._scroll_to_output = scroll_to_output
self._show_progress = show_progress
self._queue = queue
self._batch = batch
self._max_batch_size = max_batch_size
self._preprocess = preprocess
self._postprocess = postprocess
self._cancels = cancels
self._every = every
self._trigger_mode = trigger_mode
self._js = js
self._concurrency_limit = concurrency_limit
self._concurrency_id = concurrency_id
self._show_api = show_api
self.thens = thens
def setup(self) -> None:
"""
イベントリスナーの適用
GrBlocksの_set_event_listenerメソッドで実行されることで、
イベントリスナーが適用される
"""
if self.thens is None:
self._setup_without_then()
else:
self._setup_with_then()
def _setup_without_then(self) -> gr.events.Dependency:
"""
thenがないイベントリスナーの適用
Returns
-------
gr.events.Dependency
イベントリスナー
"""
if isinstance(self._trigger, list):
dep = gr.on(
triggers=self._trigger,
fn=self.fn,
inputs=self.inputs,
outputs=self.outputs,
api_name=self._api_name,
scroll_to_output=self._scroll_to_output,
show_progress=self._show_progress,
queue=self._queue,
batch=self._batch,
max_batch_size=self._max_batch_size,
preprocess=self._preprocess,
postprocess=self._postprocess,
cancels=self._cancels,
every=self._every,
trigger_mode=self._trigger_mode,
js=self._js,
concurrency_limit=self._concurrency_limit,
concurrency_id=self._concurrency_id,
show_api=self._show_api
)
else:
dep = self._trigger(
fn=self.fn,
inputs=self.inputs,
outputs=self.outputs,
api_name=self._api_name,
scroll_to_output=self._scroll_to_output,
show_progress=self._show_progress,
queue=self._queue,
batch=self._batch,
max_batch_size=self._max_batch_size,
preprocess=self._preprocess,
postprocess=self._postprocess,
cancels=self._cancels,
every=self._every,
trigger_mode=self._trigger_mode,
js=self._js,
concurrency_limit=self._concurrency_limit,
concurrency_id=self._concurrency_id,
show_api=self._show_api
)
return dep
def _setup_with_then(self) -> None:
"""
thenがあるイベントリスナーの適用
"""
dep = self._setup_without_then()
all_thens = GrListener._d_f_s_thens(self.thens)
for listener in all_thens:
dep = listener.setup_then(dep)
@staticmethod
def _d_f_s_thens(
thens: GrListener | List[GrListener], all_thens: List[GrListener] = []
) -> List[GrListener]:
"""
全てのthenの取得
thenの各要素が呼び出す全てのイベントを深さ優先探索で取得する
入れ子の様になっている全てのイベントを最後まで呼び出せるようにする
Parameters
----------
thens : GrListener | List[GrListener]
thensに代入されていたGrListenerか、そのリスト
all_thens : List[GrListener], optional
深さ優先探索で見つかったGrListenerが格納されるリスト, by default []
Returns
-------
List[GrListener]
深さ優先探索で見つかった全てのGrListenerが格納されているリスト
"""
if isinstance(thens, list):
for listener in thens:
all_thens.append(listener)
all_thens = GrListener._d_f_s_thens(listener.thens, all_thens)
elif isinstance(thens, GrListener):
all_thens.append(thens)
return all_thens
def setup_then(self, dep: gr.events.Dependency) -> gr.events.Dependency:
"""
thenの追加
イベントリスナーにthenを追加する
Parameters
----------
dep : gr.events.Dependency
thenを追加されるイベントリスナー
Returns
-------
gr.events.Dependency
thenが追加されたイベントリスナー
"""
dep.then(
fn=self.fn,
inputs=self.inputs,
outputs=self.outputs,
api_name=self._api_name,
scroll_to_output=self._scroll_to_output,
show_progress=self._show_progress,
queue=self._queue,
batch=self._batch,
max_batch_size=self._max_batch_size,
preprocess=self._preprocess,
postprocess=self._postprocess,
cancels=self._cancels,
every=self._every,
trigger_mode=self._trigger_mode,
js=self._js,
concurrency_limit=self._concurrency_limit,
concurrency_id=self._concurrency_id,
show_api=self._show_api
)
return dep
class GrExamples:
"""
gr.Componentへの入力例のクラス
詳細は公式ページを参照してください(https://www.gradio.app/docs/examples/)
"""
def __init__(
self,
examples: list[Any] | list[list[Any]] | str,
inputs: GrComponent | list[GrComponent | GrLayout] | None = None,
outputs: GrComponent | list[GrComponent | GrLayout] | None = None,
fn: Callable | None = None,
cache_examples: bool | Literal["lazy"] | None = None,
examples_per_page: int = 10,
label: str | None = "Examples",
elem_id: str | None = None,
run_on_click: bool = False,
preprocess: bool = True,
postprocess: bool = True,
api_name: str | Literal[False] = "load_example",
batch: bool = False,
listener: GrListener | None = None
):
"""
コンストラクタ
Parameters(gr.Examplesにないもの)
----------
listener: GrListener | None
インスタンス化したGrListener
これが渡されると、gr.Examplesのinputs,outputs,fnが、
渡されたリスナーと同じものになる
"""
self._examples = examples
if listener is None:
self._inputs = InputsOutputsMaker.setup_inputs_or_outputs(inputs)
self._outputs = InputsOutputsMaker.setup_inputs_or_outputs(outputs)
self._fn = fn
else:
self._inputs = listener.inputs
self._outputs = listener.outputs
self._fn = listener.fn
self._cache_examples = cache_examples
self._examples_per_page = examples_per_page
self._label = label
self._elem_id = elem_id
self._run_on_click = run_on_click
self._preprocess = preprocess
self._postprocess = postprocess
self._api_name = api_name
self._batch = batch
def set_examples(self) -> None:
"""
Examplesの適用
"""
gr.Examples(
examples=self._examples,
inputs=self._inputs,
outputs=self._outputs,
fn=self._fn,
cache_examples=self._cache_examples,
examples_per_page=self._examples_per_page,
label=self._label,
elem_id=self._elem_id,
run_on_click=self._run_on_click,
preprocess=self._preprocess,
postprocess=self._postprocess,
api_name=self._api_name,
batch=self._batch
)
class InputsOutputsMaker:
"""
inputsとoutputs調整クラス
GrListenerと、GrExamplesに渡されたinputsとoutputsを
Gradioのイベントリスナーとgr.Examplesに渡せる形にするメソッドを持つクラス
"""
@staticmethod
def setup_inputs_or_outputs(
in_or_outputs: GrComponent | GrLayout | list[GrComponent | GrLayout] | None
) -> gr.Component | List[gr.Component] | None:
"""
inputsとoutputsをgr.Componentへ変換
GrComponentから、gr.Componentである.compだけを抜き出す
Parameters
----------
in_or_outputs : GrComponent | list[GrComponent | GrLayout] | None
イベントリスナーか、gr.Examplesのinputsかoutputs対象である
gr.Componentを含むGrComponentや、GrComponentやGrLayoutのリスト
Returns
-------
gr.Component | List[gr.Component] | None
イベントリスナーかgr.Examplesの参照対象であるgr.Component
"""
if in_or_outputs is None:
return in_or_outputs
else:
if isinstance(in_or_outputs, GrComponent):
return in_or_outputs.comp
else:
return InputsOutputsMaker._create_flat_comps(in_or_outputs)
@staticmethod
def _create_flat_comps(
in_or_outputs: GrLayout | List[GrComponent | GrLayout]
) -> List[gr.Component]:
"""
gr.Componentのリストの作成
必要であればfind_compsを使って、gr.Componentのリストを作成する
Parameters
----------
in_or_outputs : List[GrComponent | GrLayout]
イベントリスナーか、gr.Examplesのinputsかoutputs対象である
gr.Componentを含むGrComponentやGrLayoutのリスト
Returns
-------
List[gr.Component]
イベントリスナーかgr.Examplesの参照対象であるgr.Componentのリスト
"""
if isinstance(in_or_outputs, GrLayout):
in_or_outputs = in_or_outputs.children
if isinstance(in_or_outputs, dict):
in_or_outputs = list(in_or_outputs.values())
comps = []
for obj in in_or_outputs:
if isinstance(obj, GrComponent):
comps.append(obj.comp)
else:
inner_flat_comps = find_comps(obj)
comps.extend([comp.comp for comp in inner_flat_comps])
return comps
class GrBlocks(ABC):
"""
ブロックのクラス
"""
@classmethod
def create_and_launch(cls, *param: Any, debug: bool = False) -> gr.Blocks:
"""
ブロックの作成と実行
Parameters
----------
debug : bool, optional, default False
デバッグをするならTrue、しないならFalse
デフォルトはFalse
Returns
-------
gr.Blocks
作成されたブロック
"""
children, listeners = cls._create_children_and_listeners(*param)
with gr.Blocks() as blocks:
GrBlocks._render_children(children)
GrBlocks._set_event_listener(listeners)
blocks.launch(debug=debug)
return blocks
@classmethod
@abstractmethod
def _create_children_and_listeners(
cls, *param: Any
) -> Tuple[Dict[str, Any] | List[Any], List[Any]]:
"""
childrenとlistenersの作成
Returns
-------
children : Dict[str, Any] | List[Any]
ブロックの子要素をまとめた辞書かリスト
内部の入れ子構造に決まりがないため、Anyとしているが、
GrLayoutとGrComponentが多重のリストや辞書になったりならなかったりする
listeners : List[Any]
イベントリスナーがまとまったリスト
内部の入れ子構造に決まりがないため、Anyとしているが、
GrListenerが多重のリストになったりならなかったりする
"""
pass
@staticmethod
def _render_children(children: Dict[str, Any] | List[Any]) -> None:
"""
子要素のrender
全ての子要素をまとめてrender()する
コンポーネントをwith gr.Blocks():内で.render()することで、ブロックに実装される
レイアウトはwith child.layout_type():をしてから子要素を.render()する
Parameters
----------
children : Dict[str, Any] | List[Any]
子要素の辞書かリスト
"""
if isinstance(children, dict):
children = list(children.values())
for child in children:
if isinstance(child, GrLayout):
with child.layout_type():
GrBlocks._render_children(child.children)
elif isinstance(child, list):
GrBlocks._render_children(child)
elif isinstance(child, GrComponent):
child.comp.render()
elif isinstance(child, GrExamples):
child.set_examples()
@staticmethod
def _set_event_listener(listeners: List[Any]) -> None:
"""
イベントリスナーの適用
全てのイベントリスナーをまとめて適用する
Parameters
----------
listeners : List[Any]
イベントリスナーのリスト
"""
for listener in listeners:
if isinstance(listener, list):
GrBlocks._set_event_listener(listener)
else:
listener.setup()
def find_comps(
children: Dict[str, GrComponent | GrLayout] | GrLayout | List[Any],
keys: List[str] | str |None = None
) -> List[GrComponent] | GrComponent:
"""
コンポーネントの検索
内包する全てのコンポーネントを検索し、
flatten_compsを用いてフラットなコンポーネントのリストを作成する
keysを渡すなら、childrenは辞書形式、keysを渡さないなら、childrenはGrLayout
childrenがGrLayoutなら内包する全てのコンポーネントをリストにして返す
同種のイベントリスナーをforで簡単に作成するために使う
Parameters
----------
children : Dict[str, GrComponent | GrLayout] | GrLayout | List[Any]
子要素の辞書か、GrLayoutか、子要素のリスト
keys : List[str] | str | None, optional, default None
辞書のキーのリストか文字列
辞書形式の子要素から、順番に深い子要素を取得していく
Returns
-------
List[GrComponent] | GrComponent
フラットなコンポーネントのリスト
"""
if isinstance(keys, list):
for key in keys:
children: GrLayout | GrComponent = find_dict_value(children, key)
elif isinstance(keys, str):
children: GrLayout | GrComponent = find_dict_value(children, keys)
else:
children: List[GrComponent] = flatten_comps(children)
return children
def find_dict_value(
children: Dict[str, GrComponent | GrLayout], key: str
) -> GrLayout | GrComponent:
"""
辞書のバリューの取得
childrenのkeyのバリューが内包するGrLayoutかGrComponentを取得する
Parameters
----------
children : Dict[str, GrComponent | GrLayout]
子要素の辞書
key : str
子要素の辞書から取得するバリューのキー
Returns
-------
GrLayout | GrComponent
取得したGrLayoutかGrComponent
"""
if isinstance(children, dict):
children: GrLayout | GrComponent = children[key]
elif isinstance(children, GrLayout):
children: GrLayout | GrComponent = children.children[key]
return children
def flatten_comps(children: GrLayout | List[Any]) -> List[GrComponent]:
"""
フラットなコンポーネントのリストの作成
find_compsから呼び出される
一つのGrLayout内で、同種のコンポーネントは
リストに直接入れられている可能性があるため、それらも含めてフラットにする
例:[comp1, comp2, [Comp(i) for i in range(10)], comp13]
Parameters
----------
children : GrLayout | List[Any]
GrLayoutか子要素のリスト
Returns
-------
List[GrComponent]
フラットなコンポーネントのリスト
"""
if isinstance(children, GrLayout):
children = children.children
if isinstance(children, dict):
children = list(children.values())
if isinstance(children, list):
return [
comp
for grandchildren in children
for comp in flatten_comps(grandchildren)
]
else:
return [children]