Skip to content

Commit e9f1c7d

Browse files
authored
Merge pull request #3 from Hum9183/release/v0.2.0
feat!: クラスベースから関数ベースのAPIに簡素化
2 parents ce81b40 + 7f3e9dc commit e9f1c7d

13 files changed

Lines changed: 168 additions & 176 deletions

README.md

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# deep_reloader
22

33
> [!WARNING]
4-
> このソフトウェアは現在プレリリース版(v0.1.1)です。APIが変更される可能性があります。
4+
> このソフトウェアは現在プレリリース版(v0.2.0)です。APIが変更される可能性があります。
55
66
Pythonモジュールの依存関係を解析して、再帰的に再読み込みを行うライブラリです。特にMayaでのスクリプト開発時に、モジュール変更を即座に反映させるために設計されています。
77

@@ -27,22 +27,17 @@ Pythonモジュールの依存関係を解析して、再帰的に再読み込
2727
### 基本的な使用方法
2828

2929
```python
30-
# 基本的な使用方法
31-
import deep_reloader
32-
dr = deep_reloader.DeepReloader()
33-
dr.reload(your_module)
34-
35-
# from-import での使用方法
36-
from deep_reloader import DeepReloader
37-
DeepReloader().reload(your_module)
30+
# 最もシンプルな使用例
31+
from deep_reloader import deep_reload
32+
deep_reload(your_module)
3833
```
3934

4035
### ログ設定
4136

4237
開発時やデバッグ時には、詳細なログ出力を有効にできます:
4338

4439
```python
45-
from deep_reloader import DeepReloader, setup_logging
40+
from deep_reloader import deep_reload, setup_logging
4641
import logging
4742

4843
# ログレベルを設定(すべてのdeep_reloaderログに影響)
@@ -52,8 +47,7 @@ logger = setup_logging(logging.DEBUG) # 詳細なデバッグ情報
5247
logger.info("deep_reloaderのログ設定が完了しました")
5348

5449
# その後、通常通り使用
55-
reloader = DeepReloader()
56-
reloader.reload(your_module)
50+
deep_reload(your_module)
5751
```
5852

5953
**ログレベルの説明:**
@@ -138,7 +132,7 @@ python -m pytest deep_reloader/tests/ -vv
138132

139133
## バージョン情報
140134

141-
**現在のバージョン**: v0.1.1 (Pre-release)
135+
**現在のバージョン**: v0.2.0 (Pre-release)
142136

143137
### リリース状況
144138
- ✅ コア機能実装完了(from-import対応)

__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
from ._metadata import __version__, __author__
1+
import logging
22

3-
from .deep_reloader import DeepReloader
4-
from .module_info import ModuleInfo
3+
from ._metadata import __author__, __version__
4+
from .deep_reloader import deep_reload
55
from .imported_symbols import ImportedSymbols
6+
from .module_info import ModuleInfo
67
from .symbol_extractor import SymbolExtractor
78

8-
import logging
9-
109

1110
def setup_logging(log_level: int = logging.INFO) -> logging.Logger:
1211
"""deep_reloader パッケージのログを設定し、パッケージロガーを返す。
@@ -36,7 +35,7 @@ def setup_logging(log_level: int = logging.INFO) -> logging.Logger:
3635

3736

3837
__all__ = [
39-
'DeepReloader',
38+
'deep_reload',
4039
'setup_logging',
4140
'ModuleInfo',
4241
'ImportedSymbols',

_metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = '0.1.1'
1+
__version__ = '0.2.0'
22
__author__ = 'Miyakawa Takeshi'

deep_reloader.py

Lines changed: 138 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import importlib
32
import logging
43
import shutil
@@ -12,145 +11,145 @@
1211
logger = logging.getLogger(__name__)
1312

1413

15-
class DeepReloader:
16-
"""再帰的なモジュールリロードを行うクラス
14+
def deep_reload(module: ModuleType) -> None:
15+
"""モジュールを再帰的にリロードする
1716
1817
Maya開発でのモジュール変更を即座に反映させるために設計されています。
18+
19+
Args:
20+
module: リロード対象のモジュール
21+
22+
Note:
23+
ログレベルの設定には setup_logging() 関数を使用してください。
24+
例: setup_logging(logging.DEBUG)
1925
"""
26+
# キャッシュを無効化して .py の変更を認識させる
27+
importlib.invalidate_caches()
28+
29+
# TODO: パフォーマンス最適化 - ファイル変更検出による差分リロード
30+
# - ファイルのタイムスタンプキャッシュで変更検出
31+
# - 変更されたモジュールのみのリロード(現在は全モジュール対象)
32+
# - AST解析結果のキャッシュ(頻繁にアクセスされるモジュール用)
33+
# - 依存関係ツリーのキャッシュ(構造変更時のみ再構築)
34+
35+
# ツリー構築(まずツリーを構築して全モジュールを把握)
36+
# TODO: ツリー構造のデバッグ出力機能を追加
37+
# - 依存関係ツリーの視覚的表示(階層構造、インデント付き)
38+
# - 各モジュールの詳細情報(パス、サイズ、最終更新時刻)
39+
# - スキップされるモジュールの理由と一覧
40+
root = _build_tree(module)
41+
42+
# ツリー全体の __pycache__ を削除
43+
_clear_pycache_recursive(root)
44+
45+
# 親→子へリロード
46+
# TODO: リロード順序とプロセスの詳細ログ追加
47+
# - 各モジュールのリロード開始/完了タイミング
48+
# - リロード中のエラーとリカバリ状況
49+
# - パフォーマンス情報(各段階の実行時間)
50+
root.reload()
51+
52+
# 子→親へシンボルをコピー(from-import で取得したシンボルを親モジュールに反映)
53+
# TODO: シンボルコピー処理の詳細ログ追加
54+
# - コピーされるシンボルの詳細(名前、型、ソース)
55+
# - シンボルの競合や上書き状況
56+
# - 失敗したシンボルとその理由
57+
root.overwrite_symbols()
58+
59+
60+
def _build_tree(module: ModuleType) -> ModuleInfo:
61+
"""
62+
AST 解析して ModuleInfo ツリーを構築
63+
64+
TODO: 組み込みモジュール(os、pathlib等)やサードパーティライブラリ(maya.cmds、PySide6等)の
65+
スキップ処理を実装する必要がある。現在は全ての依存関係をリロード対象としているため、
66+
不要なリロードや潜在的な危険性がある。
67+
"""
68+
69+
# 念のため sys.modules から最新の正規モジュールを取得する
70+
module = sys.modules[module.__name__]
71+
72+
node = ModuleInfo(module)
73+
74+
extractor = SymbolExtractor(module)
75+
for child_module, symbols in extractor.extract():
76+
child_node = _build_tree(child_module)
77+
child_node.symbols = symbols
78+
node.children.append(child_node)
2079

21-
def __init__(self) -> None:
22-
"""DeepReloader を初期化する。
23-
24-
Note:
25-
ログレベルの設定には setup_logging() 関数を使用してください。
26-
例: setup_logging(logging.DEBUG)
27-
"""
28-
pass
29-
30-
def reload(self, module: ModuleType) -> None:
31-
# キャッシュを無効化して .py の変更を認識させる
32-
importlib.invalidate_caches()
33-
34-
# TODO: パフォーマンス最適化 - ファイル変更検出による差分リロード
35-
# - ファイルのタイムスタンプキャッシュで変更検出
36-
# - 変更されたモジュールのみのリロード(現在は全モジュール対象)
37-
# - AST解析結果のキャッシュ(頻繁にアクセスされるモジュール用)
38-
# - 依存関係ツリーのキャッシュ(構造変更時のみ再構築)
39-
40-
# ツリー構築(まずツリーを構築して全モジュールを把握)
41-
# TODO: ツリー構造のデバッグ出力機能を追加
42-
# - 依存関係ツリーの視覚的表示(階層構造、インデント付き)
43-
# - 各モジュールの詳細情報(パス、サイズ、最終更新時刻)
44-
# - スキップされるモジュールの理由と一覧
45-
root = self._build_tree(module)
46-
47-
# ツリー全体の __pycache__ を削除
48-
self._clear_pycache_recursive(root)
49-
50-
# 親→子へリロード
51-
# TODO: リロード順序とプロセスの詳細ログ追加
52-
# - 各モジュールのリロード開始/完了タイミング
53-
# - リロード中のエラーとリカバリ状況
54-
# - パフォーマンス情報(各段階の実行時間)
55-
root.reload()
56-
57-
# 子→親へシンボルをコピー(from-import で取得したシンボルを親モジュールに反映)
58-
# TODO: シンボルコピー処理の詳細ログ追加
59-
# - コピーされるシンボルの詳細(名前、型、ソース)
60-
# - シンボルの競合や上書き状況
61-
# - 失敗したシンボルとその理由
62-
root.overwrite_symbols()
63-
64-
def _build_tree(self, module: ModuleType) -> ModuleInfo:
65-
"""
66-
AST 解析して ModuleInfo ツリーを構築
67-
68-
TODO: 組み込みモジュール(os、pathlib等)やサードパーティライブラリ(maya.cmds、PySide6等)の
69-
スキップ処理を実装する必要がある。現在は全ての依存関係をリロード対象としているため、
70-
不要なリロードや潜在的な危険性がある。
71-
"""
72-
73-
# 念のため sys.modules から最新の正規モジュールを取得する
74-
module = sys.modules[module.__name__]
75-
76-
node = ModuleInfo(module)
77-
78-
extractor = SymbolExtractor(module)
79-
for child_module, symbols in extractor.extract():
80-
child_node = self._build_tree(child_module)
81-
child_node.symbols = symbols
82-
node.children.append(child_node)
83-
84-
return node
85-
86-
def _clear_pycache_recursive(self, node: ModuleInfo) -> None:
87-
"""
88-
ModuleInfo ツリー全体を再帰的にたどって __pycache__ を削除
89-
"""
90-
self._clear_single_pycache(node.module)
91-
for child in node.children:
92-
self._clear_pycache_recursive(child)
93-
94-
def _clear_single_pycache(self, module: ModuleType) -> None:
95-
"""
96-
1つのモジュールに対応する __pycache__ を削除
97-
"""
98-
module_file = getattr(module, '__file__', None)
99-
if module_file is None:
100-
return
101-
102-
module_dir = Path(module_file).parent
103-
pycache_dir = module_dir / '__pycache__'
104-
105-
if pycache_dir.exists():
106-
try:
107-
shutil.rmtree(pycache_dir)
108-
logger.debug(f'Cleared pycache {pycache_dir}')
109-
except Exception as e:
110-
logger.warning(f'Failed to clear pycache {pycache_dir}: {e!r}')
111-
112-
# TODO: 将来の実装用メソッド - デバッグ情報の出力機能
113-
# def _print_tree_structure(self, root: ModuleInfo, level: int = 0) -> None:
114-
# """依存関係ツリーを視覚的に表示する
115-
#
116-
# 出力例:
117-
# my_package.main
118-
# ├── my_package.utils (from utils import helper, calculator)
119-
# │ └── my_package.math_utils (from .math_utils import add, subtract)
120-
# ├── my_package.config (from .config import settings)
121-
# └── os [SKIPPED: builtin module]
122-
# """
123-
# pass
124-
#
125-
# def _log_reload_summary(self, root: ModuleInfo) -> None:
126-
# """リロード処理の概要を詳細ログ出力
127-
#
128-
# 出力内容:
129-
# - 処理開始/終了時刻
130-
# - 総モジュール数、リロード対象数、スキップ数
131-
# - 各段階の所要時間(ツリー構築、リロード、シンボルコピー)
132-
# - 検出されたエラーや警告の統計
133-
# """
134-
# pass
135-
#
136-
# TODO: 将来の実装用 - パフォーマンス最適化キャッシュシステム
137-
# def _init_cache_system(self) -> None:
138-
# """キャッシュシステムの初期化
139-
#
140-
# キャッシュ対象:
141-
# - ファイルタイムスタンプ(変更検出用)
142-
# - AST解析結果(重いパース処理の削減)
143-
# - 依存関係ツリー(構造変更時のみ再構築)
144-
# - モジュール判定結果(組み込み/サードパーティ判定キャッシュ)
145-
# """
146-
# pass
147-
#
148-
# def _should_reload_module(self, module: ModuleType) -> bool:
149-
# """モジュールがリロード必要かキャッシュベースで判定
150-
#
151-
# 判定基準:
152-
# - ファイルタイムスタンプの変更
153-
# - 依存関係の変更
154-
# - 強制リロードフラグ
155-
# """
156-
# pass
80+
return node
81+
82+
83+
def _clear_pycache_recursive(node: ModuleInfo) -> None:
84+
"""
85+
ModuleInfo ツリー全体を再帰的にたどって __pycache__ を削除
86+
"""
87+
_clear_single_pycache(node.module)
88+
for child in node.children:
89+
_clear_pycache_recursive(child)
90+
91+
92+
def _clear_single_pycache(module: ModuleType) -> None:
93+
"""
94+
1つのモジュールに対応する __pycache__ を削除
95+
"""
96+
module_file = getattr(module, '__file__', None)
97+
if module_file is None:
98+
return
99+
100+
module_dir = Path(module_file).parent
101+
pycache_dir = module_dir / '__pycache__'
102+
103+
if pycache_dir.exists():
104+
try:
105+
shutil.rmtree(pycache_dir)
106+
logger.debug(f'Cleared pycache {pycache_dir}')
107+
except Exception as e:
108+
logger.warning(f'Failed to clear pycache {pycache_dir}: {e!r}')
109+
110+
111+
# TODO: 将来の実装用メソッド - デバッグ情報の出力機能
112+
# def _print_tree_structure(root: ModuleInfo, level: int = 0) -> None:
113+
# """依存関係ツリーを視覚的に表示する
114+
#
115+
# 出力例:
116+
# my_package.main
117+
# ├── my_package.utils (from utils import helper, calculator)
118+
# │ └── my_package.math_utils (from .math_utils import add, subtract)
119+
# ├── my_package.config (from .config import settings)
120+
# └── os [SKIPPED: builtin module]
121+
# """
122+
# pass
123+
#
124+
# def _log_reload_summary(root: ModuleInfo) -> None:
125+
# """リロード処理の概要を詳細ログ出力
126+
#
127+
# 出力内容:
128+
# - 処理開始/終了時刻
129+
# - 総モジュール数、リロード対象数、スキップ数
130+
# - 各段階の所要時間(ツリー構築、リロード、シンボルコピー)
131+
# - 検出されたエラーや警告の統計
132+
# """
133+
# pass
134+
#
135+
# TODO: 将来の実装用 - パフォーマンス最適化キャッシュシステム
136+
# def _init_cache_system() -> None:
137+
# """キャッシュシステムの初期化
138+
#
139+
# キャッシュ対象:
140+
# - ファイルタイムスタンプ(変更検出用)
141+
# - AST解析結果(重いパース処理の削減)
142+
# - 依存関係ツリー(構造変更時のみ再構築)
143+
# - モジュール判定結果(組み込み/サードパーティ判定キャッシュ)
144+
# """
145+
# pass
146+
#
147+
# def _should_reload_module(module: ModuleType) -> bool:
148+
# """モジュールがリロード必要かキャッシュベースで判定
149+
#
150+
# 判定基準:
151+
# - ファイルタイムスタンプの変更
152+
# - 依存関係の変更
153+
# - 強制リロードフラグ
154+
# """
155+
# pass

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
スクリプト実行時はtest_utilsを直接使用するため、このファイルをインポートする必要はない。
66
"""
77

8-
import pytest
8+
import pytest # type: ignore # noqa: F401
99

1010
from .test_utils import clear_test_environment
1111

tests/test_absolute_import_basic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ def test_simple_from_import_reload(tmp_path):
4141
(tmp_path / 'a.py').write_text('x = 999\n', encoding='utf-8')
4242

4343
# deep reloadを実行
44-
import deep_reloader as dr
44+
from deep_reloader import deep_reload
4545

46-
dr.DeepReloader().reload(b)
46+
deep_reload(b)
4747

4848
# 更新された値を確認
4949
new_b = importlib.import_module('b')

0 commit comments

Comments
 (0)