|
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] |
|
|
|
|