SessionSmith 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sessionsmith-0.1.2/LICENSE +22 -0
- sessionsmith-0.1.2/PKG-INFO +224 -0
- sessionsmith-0.1.2/SessionSmith/__init__.py +33 -0
- sessionsmith-0.1.2/SessionSmith/compare.py +151 -0
- sessionsmith-0.1.2/SessionSmith/core.py +389 -0
- sessionsmith-0.1.2/SessionSmith/formats.py +416 -0
- sessionsmith-0.1.2/SessionSmith/info.py +177 -0
- sessionsmith-0.1.2/SessionSmith/jupyter_utils.py +63 -0
- sessionsmith-0.1.2/SessionSmith/manager.py +537 -0
- sessionsmith-0.1.2/SessionSmith/serializers.py +134 -0
- sessionsmith-0.1.2/SessionSmith/tracer.py +389 -0
- sessionsmith-0.1.2/SessionSmith/utils.py +127 -0
- sessionsmith-0.1.2/SessionSmith/version_control.py +356 -0
- sessionsmith-0.1.2/SessionSmith/visualizer.py +229 -0
- sessionsmith-0.1.2/SessionSmith/visualizer_arrays.py +227 -0
- sessionsmith-0.1.2/SessionSmith/visualizer_generic.py +78 -0
- sessionsmith-0.1.2/SessionSmith.egg-info/PKG-INFO +224 -0
- sessionsmith-0.1.2/SessionSmith.egg-info/SOURCES.txt +21 -0
- sessionsmith-0.1.2/SessionSmith.egg-info/requires.txt +6 -0
- sessionsmith-0.1.2/SessionSmith.egg-info/top_level.txt +1 -0
- sessionsmith-0.1.2/setup.py +63 -0
- sessionsmith-0.1.0/PKG-INFO +0 -58
- sessionsmith-0.1.0/SessionSmith.egg-info/PKG-INFO +0 -58
- sessionsmith-0.1.0/SessionSmith.egg-info/SOURCES.txt +0 -6
- sessionsmith-0.1.0/SessionSmith.egg-info/requires.txt +0 -1
- sessionsmith-0.1.0/SessionSmith.egg-info/top_level.txt +0 -1
- sessionsmith-0.1.0/setup.py +0 -24
- {sessionsmith-0.1.0 → sessionsmith-0.1.2}/SessionSmith.egg-info/dependency_links.txt +0 -0
- {sessionsmith-0.1.0 → sessionsmith-0.1.2}/setup.cfg +0 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 YutoTAKAGI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SessionSmith
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Simple session save/load utility for Jupyter notebooks using pickle
|
|
5
|
+
Home-page: https://github.com/yut0takagi/SessionSmith
|
|
6
|
+
Author: YutoTAKAGI
|
|
7
|
+
Author-email: yutotkg.1040@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: jupyter notebook session save load pickle serialization algorithm tracer visualization
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering
|
|
17
|
+
Classifier: Framework :: Jupyter
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: visualization
|
|
22
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "visualization"
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "all"
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: home-page
|
|
31
|
+
Dynamic: keywords
|
|
32
|
+
Dynamic: license
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
Dynamic: provides-extra
|
|
35
|
+
Dynamic: requires-python
|
|
36
|
+
Dynamic: summary
|
|
37
|
+
|
|
38
|
+
# SessionSmith
|
|
39
|
+
|
|
40
|
+
**SessionSmith** は、Jupyter Notebook や Python 実行時のセッション(変数・オブジェクト)を簡単に保存・復元できる軽量ライブラリです。
|
|
41
|
+
|
|
42
|
+
## ⚠️ セキュリティ警告
|
|
43
|
+
|
|
44
|
+
**重要**: このライブラリは`pickle`を使用しています。信頼できないソースからのセッションファイルをロードしないでください。悪意のあるpickleファイルは任意のコードを実行する可能性があります。
|
|
45
|
+
|
|
46
|
+
- ✅ 信頼できるソースからのファイルのみをロードしてください
|
|
47
|
+
- ✅ 不審なファイルをロードする前に、`verify_session()`で検証してください
|
|
48
|
+
- ✅ 本番環境では、セッションファイルの保存場所へのアクセスを制限してください
|
|
49
|
+
|
|
50
|
+
## 特徴
|
|
51
|
+
|
|
52
|
+
- 🚀 **簡単**: たった2行で保存&復元
|
|
53
|
+
- 📦 **複数形式対応**: pickle(デフォルト)、JSON、MessagePack、HDF5
|
|
54
|
+
- 🔍 **自動検出**: ファイル拡張子から形式を自動検出
|
|
55
|
+
- 🗜️ **圧縮対応**: gzip/bz2圧縮でディスク容量を節約
|
|
56
|
+
- 📊 **情報表示**: セッションの詳細情報を確認
|
|
57
|
+
- 🔄 **比較機能**: 2つのセッションを比較
|
|
58
|
+
- 💾 **自動バックアップ**: 定期的な自動保存
|
|
59
|
+
- 🏷️ **バージョン管理**: Git風のコミット・チェックアウト機能
|
|
60
|
+
- 📈 **アルゴリズムトレーサー**: 1行ごとの変数状態記録・可視化
|
|
61
|
+
- 🎨 **可視化**: アルゴリズムの実行をアニメーションで表示
|
|
62
|
+
|
|
63
|
+
## インストール
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install SessionSmith
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
可視化機能を使う場合:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install SessionSmith[visualization]
|
|
73
|
+
# または
|
|
74
|
+
pip install matplotlib
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## クイックスタート
|
|
78
|
+
|
|
79
|
+
### 基本的な使い方
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from SessionSmith import save_session, load_session
|
|
83
|
+
|
|
84
|
+
# セッション保存(pickle形式、デフォルト)
|
|
85
|
+
save_session("my_session.pkl")
|
|
86
|
+
|
|
87
|
+
# セッション復元
|
|
88
|
+
load_session("my_session.pkl")
|
|
89
|
+
|
|
90
|
+
# JSON形式で保存(安全、可読性)
|
|
91
|
+
save_session("my_session.json")
|
|
92
|
+
|
|
93
|
+
# MessagePack形式で保存(安全、高速)
|
|
94
|
+
save_session("my_session.msgpack", format="msgpack")
|
|
95
|
+
|
|
96
|
+
# HDF5形式で保存(科学計算データに最適)
|
|
97
|
+
save_session("my_session.h5", format="hdf5")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### SessionManagerを使う
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from SessionSmith import SessionManager
|
|
104
|
+
|
|
105
|
+
# マネージャーを作成
|
|
106
|
+
manager = SessionManager()
|
|
107
|
+
|
|
108
|
+
# 保存
|
|
109
|
+
manager.save("session.pkl")
|
|
110
|
+
|
|
111
|
+
# ロード
|
|
112
|
+
manager.load("session.pkl")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### バージョン管理(新機能)
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from SessionSmith import SessionManager
|
|
119
|
+
|
|
120
|
+
# バージョン管理を有効化
|
|
121
|
+
manager = SessionManager(enable_version_control=True)
|
|
122
|
+
|
|
123
|
+
# 保存(自動的にコミット)
|
|
124
|
+
manager.save("session.pkl", commit_message="Initial state")
|
|
125
|
+
|
|
126
|
+
# コミット履歴を確認
|
|
127
|
+
manager.log()
|
|
128
|
+
|
|
129
|
+
# 以前の状態に戻す
|
|
130
|
+
manager.checkout(message="Initial state")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 主な機能
|
|
134
|
+
|
|
135
|
+
### 1. 基本的な保存・復元
|
|
136
|
+
- 変数の選択的保存・復元
|
|
137
|
+
- 圧縮サポート(gzip/bz2)
|
|
138
|
+
- Jupyter Notebook内部変数の自動除外
|
|
139
|
+
|
|
140
|
+
### 2. SessionManagerクラス
|
|
141
|
+
- セッション管理の簡素化
|
|
142
|
+
- 自動バックアップ機能
|
|
143
|
+
- バージョン管理機能(Git風)
|
|
144
|
+
|
|
145
|
+
### 3. セッション情報・比較
|
|
146
|
+
- セッション情報の表示
|
|
147
|
+
- 2つのセッションの比較
|
|
148
|
+
- セッションファイルの検証
|
|
149
|
+
|
|
150
|
+
### 4. アルゴリズム実行トレーサー
|
|
151
|
+
- 1行ごとの変数状態記録
|
|
152
|
+
- 可視化(アニメーション)
|
|
153
|
+
- トレースデータの保存・読み込み
|
|
154
|
+
|
|
155
|
+
### 5. カスタムシリアライザー
|
|
156
|
+
- 特定の型に対するカスタムシリアライゼーション
|
|
157
|
+
- 拡張可能な設計
|
|
158
|
+
|
|
159
|
+
## 詳細なドキュメント
|
|
160
|
+
|
|
161
|
+
詳細な使い方は以下のドキュメントを参照してください:
|
|
162
|
+
|
|
163
|
+
- 📖 [基本的な使い方](docs/getting-started.md) - 保存・復元の詳細
|
|
164
|
+
- 🔄 [バージョン管理](docs/version-control.md) - Git風のバージョン管理機能
|
|
165
|
+
- 📈 [アルゴリズムトレーサー](docs/algorithm-tracer.md) - トレース・可視化機能
|
|
166
|
+
- 📚 [APIリファレンス](docs/api-reference.md) - 全APIの詳細
|
|
167
|
+
|
|
168
|
+
## 使用例
|
|
169
|
+
|
|
170
|
+
### 複数形式のサポート
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
# pickle形式(デフォルト、互換性重視)
|
|
174
|
+
save_session("session.pkl")
|
|
175
|
+
|
|
176
|
+
# JSON形式(安全、可読性)
|
|
177
|
+
save_session("session.json")
|
|
178
|
+
|
|
179
|
+
# MessagePack形式(安全、高速)
|
|
180
|
+
save_session("session.msgpack", format="msgpack")
|
|
181
|
+
|
|
182
|
+
# HDF5形式(科学計算データに最適)
|
|
183
|
+
save_session("session.h5", format="hdf5")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 圧縮保存
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# gzip圧縮で保存
|
|
190
|
+
save_session("session.pkl", compress=True)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### セレクティブロード
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# 特定の変数のみ復元
|
|
197
|
+
load_session("session.pkl", include=["data", "model"])
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 自動バックアップ
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
manager = SessionManager()
|
|
204
|
+
manager.auto_save(interval=300, file_path="autosave.pkl")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### アルゴリズム可視化
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from SessionSmith import AlgorithmTracer, visualize_algorithm_trace
|
|
211
|
+
|
|
212
|
+
with AlgorithmTracer(target_variables=["arr"]) as tracer:
|
|
213
|
+
bubble_sort(arr)
|
|
214
|
+
|
|
215
|
+
visualize_algorithm_trace(
|
|
216
|
+
trace_data=tracer.get_trace_data(),
|
|
217
|
+
output_file="animation.gif",
|
|
218
|
+
target_variables=["arr"]
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## ライセンス
|
|
223
|
+
|
|
224
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SessionSmith - セッション保存・復元ライブラリ
|
|
3
|
+
|
|
4
|
+
堅牢で寛容なセッション管理ライブラリです。
|
|
5
|
+
Jupyter Notebook環境での使用に最適化されています。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .core import save_session, load_session
|
|
9
|
+
from .manager import SessionManager
|
|
10
|
+
from .info import get_session_info, list_session_variables, print_session_info
|
|
11
|
+
from .compare import compare_sessions, print_comparison
|
|
12
|
+
from .utils import verify_session
|
|
13
|
+
from .serializers import CustomSerializer
|
|
14
|
+
from .tracer import AlgorithmTracer
|
|
15
|
+
from .visualizer import visualize_algorithm_trace, print_trace_summary
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.2"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"save_session",
|
|
21
|
+
"load_session",
|
|
22
|
+
"SessionManager",
|
|
23
|
+
"get_session_info",
|
|
24
|
+
"list_session_variables",
|
|
25
|
+
"print_session_info",
|
|
26
|
+
"compare_sessions",
|
|
27
|
+
"print_comparison",
|
|
28
|
+
"verify_session",
|
|
29
|
+
"CustomSerializer",
|
|
30
|
+
"AlgorithmTracer",
|
|
31
|
+
"visualize_algorithm_trace",
|
|
32
|
+
"print_trace_summary",
|
|
33
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
セッション比較機能
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Set, Any, Union
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import warnings
|
|
8
|
+
from .core import load_session
|
|
9
|
+
from .info import list_session_variables
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def compare_sessions(
|
|
13
|
+
file_path1: Union[str, Path],
|
|
14
|
+
file_path2: Union[str, Path],
|
|
15
|
+
detailed: bool = False
|
|
16
|
+
) -> Dict[str, Any]:
|
|
17
|
+
"""
|
|
18
|
+
2つのセッションファイルを比較します
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
file_path1: 最初のセッションファイルのパス
|
|
22
|
+
file_path2: 2番目のセッションファイルのパス
|
|
23
|
+
detailed: 詳細な比較情報を含めるか(値の変更を検出)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
dict: 比較結果(追加、削除、変更された変数のリスト)
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
FileNotFoundError: いずれかのファイルが存在しない場合
|
|
30
|
+
IOError: ファイルの読み込みに失敗した場合
|
|
31
|
+
"""
|
|
32
|
+
def _resolve_and_check(path: Union[str, Path]) -> Path:
|
|
33
|
+
p = Path(path)
|
|
34
|
+
if not p.exists():
|
|
35
|
+
raise FileNotFoundError(f"Session file '{p}' not found.")
|
|
36
|
+
return p
|
|
37
|
+
|
|
38
|
+
file_path1 = _resolve_and_check(file_path1)
|
|
39
|
+
file_path2 = _resolve_and_check(file_path2)
|
|
40
|
+
|
|
41
|
+
def _get_variable_set(path: Path) -> Set[str]:
|
|
42
|
+
try:
|
|
43
|
+
return set(list_session_variables(path))
|
|
44
|
+
except Exception as e:
|
|
45
|
+
raise IOError(f"Failed to read session variables: {str(e)}") from e
|
|
46
|
+
|
|
47
|
+
vars1 = _get_variable_set(file_path1)
|
|
48
|
+
vars2 = _get_variable_set(file_path2)
|
|
49
|
+
|
|
50
|
+
# 共通変数、追加、削除を計算
|
|
51
|
+
common = vars1 & vars2
|
|
52
|
+
added = vars2 - vars1
|
|
53
|
+
removed = vars1 - vars2
|
|
54
|
+
|
|
55
|
+
result: Dict[str, Any] = {
|
|
56
|
+
"file1": str(file_path1),
|
|
57
|
+
"file2": str(file_path2),
|
|
58
|
+
"common_variables": sorted(list(common)),
|
|
59
|
+
"added_variables": sorted(list(added)),
|
|
60
|
+
"removed_variables": sorted(list(removed)),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# 詳細な比較(値の変更を検出)
|
|
64
|
+
if detailed:
|
|
65
|
+
changed: List[str] = []
|
|
66
|
+
# 一時的な名前空間でロードして比較
|
|
67
|
+
temp_globals1: Dict[str, Any] = {}
|
|
68
|
+
temp_globals2: Dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
load_session(file_path1, globals_dict=temp_globals1)
|
|
72
|
+
load_session(file_path2, globals_dict=temp_globals2)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
raise IOError(f"Failed to load sessions for comparison: {str(e)}") from e
|
|
75
|
+
|
|
76
|
+
for var_name in common:
|
|
77
|
+
val1 = temp_globals1.get(var_name)
|
|
78
|
+
val2 = temp_globals2.get(var_name)
|
|
79
|
+
|
|
80
|
+
# 値が異なるかチェック(簡易的な比較)
|
|
81
|
+
try:
|
|
82
|
+
import pickle
|
|
83
|
+
# 両方の値がNoneの場合は同じとみなす
|
|
84
|
+
if val1 is None and val2 is None:
|
|
85
|
+
continue
|
|
86
|
+
# 一方がNoneの場合は変更されたとみなす
|
|
87
|
+
if val1 is None or val2 is None:
|
|
88
|
+
changed.append(var_name)
|
|
89
|
+
continue
|
|
90
|
+
# pickleで比較
|
|
91
|
+
if pickle.dumps(val1) != pickle.dumps(val2):
|
|
92
|
+
changed.append(var_name)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
# 比較できない場合は変更されたとみなす
|
|
95
|
+
warnings.warn(
|
|
96
|
+
f"Could not compare variable '{var_name}': {str(e)}",
|
|
97
|
+
UserWarning
|
|
98
|
+
)
|
|
99
|
+
changed.append(var_name)
|
|
100
|
+
|
|
101
|
+
result["changed_variables"] = changed
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def print_comparison(
|
|
107
|
+
file_path1: Union[str, Path],
|
|
108
|
+
file_path2: Union[str, Path],
|
|
109
|
+
detailed: bool = False
|
|
110
|
+
) -> None:
|
|
111
|
+
"""
|
|
112
|
+
セッション比較結果を整形して表示します
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
file_path1: 最初のセッションファイルのパス
|
|
116
|
+
file_path2: 2番目のセッションファイルのパス
|
|
117
|
+
detailed: 詳細な比較情報を含めるか
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
FileNotFoundError: いずれかのファイルが存在しない場合
|
|
121
|
+
IOError: ファイルの読み込みに失敗した場合
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
result = compare_sessions(file_path1, file_path2, detailed=detailed)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Error comparing sessions: {e}")
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
print(f"Comparison: {result['file1']} vs {result['file2']}\n")
|
|
130
|
+
|
|
131
|
+
if result['common_variables']:
|
|
132
|
+
print(f"Common variables ({len(result['common_variables'])}):")
|
|
133
|
+
for var in result['common_variables']:
|
|
134
|
+
print(f" {var}")
|
|
135
|
+
else:
|
|
136
|
+
print("Common variables: None")
|
|
137
|
+
|
|
138
|
+
if result['added_variables']:
|
|
139
|
+
print(f"\nAdded variables ({len(result['added_variables'])}):")
|
|
140
|
+
for var in result['added_variables']:
|
|
141
|
+
print(f" + {var}")
|
|
142
|
+
|
|
143
|
+
if result['removed_variables']:
|
|
144
|
+
print(f"\nRemoved variables ({len(result['removed_variables'])}):")
|
|
145
|
+
for var in result['removed_variables']:
|
|
146
|
+
print(f" - {var}")
|
|
147
|
+
|
|
148
|
+
if detailed and result.get('changed_variables'):
|
|
149
|
+
print(f"\nChanged variables ({len(result['changed_variables'])}):")
|
|
150
|
+
for var in result['changed_variables']:
|
|
151
|
+
print(f" ~ {var}")
|