C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

動的にモジュールをインポートする3

こちらの続きです。
動的にモジュールをインポートする2 - C#ATIA

もうちょっと狙っている状態に近づけます。

test.pyを実行して、fuga,hoge,piyoのrun関数を呼び出す事には
変わらないのですが、後々の事を考え "commands" フォルダ内に
各フォルダを設置し、それぞれにpyファイルを入れた状態です。

│  test.manifest
│  test.py
│
└─commands
    ├─fuga
    │      fuga.py
    │
    ├─hoge
    │      hoge.py
    │
    └─piyo
            piyo.py

これを動的にモジュールとして読み込み、関数実行です。
こんな感じで出来ました。

# fusion360 API python

import traceback
import adsk.core
import adsk.fusion
import sys
import pathlib
import importlib
import inspect
import collections

# このフォルダーをパスに追加
THIS_DIR = pathlib.Path(__file__).resolve().parent
if str(THIS_DIR) not in sys.path:
    sys.path.append(str(THIS_DIR))

COMMANDS_DIR = THIS_DIR / 'commands'

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface

        # run関数を持つモジュールの取得
        modules = getHasRunModules()

        # run関数実行
        [module.run(context) for module in modules]

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))


# https://note.nkmk.me/python-list-flatten/
def flatten(l):
    for el in l:
        if isinstance(el, collections.abc.Iterable) and not isinstance(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el

def getHasRunModules() -> list:

    # commandsフォルダ内の子フォルダ内のpyファイルの取得
    files = flatten([d.iterdir() for d in COMMANDS_DIR.iterdir() if d.is_dir()])
    pyFiles = [f for f in files if f.is_file() and f.suffix == '.py']

    # run関数を持つモジュールの取得
    modules = []
    for file in pyFiles:

        # 各モジュールの親フォルダのパス追加
        path = str(file.parent)
        if path not in sys.path:
            sys.path.append(path)

        # モジュールとしてインポート
        module = importlib.import_module(file.stem)
        importlib.reload(module)

        # パス削除
        sys.path.remove(path)

        # モジュール内から関数名取得
        funcs = [func for func, _ in inspect.getmembers(module, inspect.isfunction)]

        if 'run' in funcs:
            # run関数を持っている
            modules.append(module)
        else:
            # モジュールの破棄
            del module

    return modules

import している量が多いな・・・。
commandsフォルダ内の子フォルダ全てパスを通さないと、
importlib.import_moduleでエラーになりました。多分。

これで、コマンドをフォルダ毎に分けられるし、メニュー用の
アドイン自体と各コマンドが分かれている上、動的に
呼びさせるので、登録・削除が楽になりつつ各コマンド毎に
スッキリした状態になりそうです。