Spaces:
Sleeping
Sleeping
""" | |
Gradio app: 入力されたpip依存関係(requirements.txtスタイル or パッケージリスト)を受け取り、 | |
指定したディレクトリに`pip install --target`でパッケージ(依存関係含む)をインストールし、 | |
その内容をZIPにまとめてダウンロードできるようにします。 | |
使い方: | |
- requirements テキストボックスにパッケージを1行ごとに書くか、requirements.txtの中身を貼る | |
- "依存関係を含める" のチェックを入れると pip が依存も自動でインストールします(デフォルト: True) | |
- "実行" を押すと処理が行われ、ZIPファイルとログが出力されます | |
注意事項: | |
- 任意のパッケージのインストールは任意のコード実行を伴うため、悪意あるパッケージをインストールすると | |
サーバーに危害が及ぶ可能性があります。公開サーバーでの運用は十分注意してください。 | |
- ネイティブ拡張(C拡張)を含むパッケージはビルドが必要で、ビルド環境が整っていないと失敗します。 | |
- 大きなパッケージや多数の依存関係は時間がかかる場合があります。 | |
このファイルは1つのPythonスクリプトとして実行できます (Python 3.8+ 推奨)。 | |
前提: gradio がインストールされていること。 | |
例: | |
python gradio_pip_installer_zipper.py | |
""" | |
import gradio as gr | |
import tempfile | |
import shutil | |
import subprocess | |
import sys | |
import os | |
import zipfile | |
from pathlib import Path | |
import time | |
def install_and_zip(requirements_text: str, include_deps: bool = True, zip_name_prefix: str = "packages"): | |
"""requirements_text: requirements.txt の中身またはパッケージを改行区切りで受け取る。 | |
include_deps: True のとき pip は依存も解決してインストールする。 | |
戻り値: (zip_path, log_text) | |
""" | |
start_ts = time.time() | |
log_lines = [] | |
if not requirements_text or not requirements_text.strip(): | |
return None, "エラー: requirements の入力が空です。" | |
# 一時ディレクトリを作成 | |
work_dir = Path(tempfile.mkdtemp(prefix="pip_zipper_")) | |
target_dir = work_dir / "site-packages" | |
target_dir.mkdir(parents=True, exist_ok=True) | |
req_file = work_dir / "requirements.txt" | |
# クリーンな requirements.txt を作る(空行とコメントを除去) | |
pkgs = [] | |
for line in requirements_text.splitlines(): | |
s = line.split("#", 1)[0].strip() | |
if s: | |
pkgs.append(s) | |
if not pkgs: | |
shutil.rmtree(work_dir, ignore_errors=True) | |
return None, "エラー: 有効なパッケージ指定が見つかりませんでした。" | |
req_file.write_text("\n".join(pkgs), encoding="utf-8") | |
log_lines.append(f"作業ディレクトリ: {work_dir}") | |
log_lines.append(f"インストール先ディレクトリ: {target_dir}") | |
log_lines.append(f"requirements:\n" + "\n".join(pkgs)) | |
# pip コマンドの構築 | |
pip_cmd = [sys.executable, "-m", "pip", "install", "-r", str(req_file), "--target", str(target_dir)] | |
if not include_deps: | |
pip_cmd.append("--no-deps") | |
# 環境によっては pip が古くて失敗することがあるため最小限のアップグレードを試みる(任意) | |
try: | |
log_lines.append("pip のバージョン確認...") | |
out = subprocess.check_output([sys.executable, "-m", "pip", "--version"], stderr=subprocess.STDOUT, text=True) | |
log_lines.append(out.strip()) | |
except subprocess.CalledProcessError as e: | |
log_lines.append("pip バージョン確認に失敗しました: " + str(e)) | |
# 実行 | |
log_lines.append("\n=== pip install を実行します ===") | |
log_lines.append("実行コマンド: " + " ".join(pip_cmd)) | |
try: | |
proc = subprocess.run(pip_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=str(work_dir), timeout=60*30) | |
log_lines.append(proc.stdout) | |
if proc.returncode != 0: | |
log_lines.append(f"pip install は終了コード {proc.returncode} で失敗しました。") | |
# ZIP は作らず一時ディレクトリを残してログを返す | |
zip_path = None | |
total_time = time.time() - start_ts | |
log_lines.append(f"経過時間: {total_time:.1f}s") | |
return zip_path, "\n".join(log_lines) | |
except subprocess.TimeoutExpired: | |
log_lines.append("pip install がタイムアウトしました。") | |
shutil.rmtree(work_dir, ignore_errors=True) | |
return None, "\n".join(log_lines) | |
except Exception as e: | |
log_lines.append("予期せぬエラー: " + str(e)) | |
shutil.rmtree(work_dir, ignore_errors=True) | |
return None, "\n".join(log_lines) | |
# 成功したら ZIP を作る | |
zip_filename = f"{zip_name_prefix}_{int(time.time())}.zip" | |
zip_path = work_dir / zip_filename | |
log_lines.append("\n=== ZIP 生成 ===") | |
try: | |
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf: | |
# target_dir 以下をすべて追加 | |
for root, dirs, files in os.walk(target_dir): | |
for file in files: | |
fullpath = Path(root) / file | |
# ZIP 内のパスは target_dir の相対パスにする | |
relpath = fullpath.relative_to(work_dir) | |
zf.write(fullpath, arcname=str(relpath)) | |
log_lines.append(f"ZIP 作成完了: {zip_path} (サイズ: {zip_path.stat().st_size} bytes)") | |
except Exception as e: | |
log_lines.append("ZIP 作成中にエラー: " + str(e)) | |
shutil.rmtree(work_dir, ignore_errors=True) | |
return None, "\n".join(log_lines) | |
total_time = time.time() - start_ts | |
log_lines.append(f"経過時間: {total_time:.1f}s") | |
# 注意: 一時ディレクトリは残しておく(ユーザーがZIPをダウンロードできるように) | |
# 運用サーバーであれば定期クリーンアップを別途実装してください。 | |
return str(zip_path), "\n".join(log_lines) | |
# Gradio UI | |
with gr.Blocks() as demo: | |
gr.Markdown("""# Pip 依存関係インストール → ZIP 生成 | |
下に requirements.txt の中身、または1行ごとのパッケージ名を貼り付けてください。 | |
""") | |
with gr.Row(): | |
req_input = gr.Textbox(label="requirements またはパッケージ一覧 (1行に1件)", lines=10, placeholder="例:\nrequests==2.31.0\nnumpy>=1.25\n# コメントは無視されます") | |
with gr.Row(): | |
include_deps = gr.Checkbox(value=True, label="依存関係を含める (通常はオン) ") | |
prefix = gr.Textbox(value="packages", label="生成ZIPのプレフィックス (任意)") | |
run_btn = gr.Button("実行") | |
output_file = gr.File(label="ダウンロード ZIP") | |
log_box = gr.Textbox(label="実行ログ", lines=15) | |
def _run(req_text, incl_deps, pref): | |
zip_path, log = install_and_zip(req_text, include_deps=incl_deps, zip_name_prefix=(pref or "packages")) | |
if zip_path: | |
return zip_path, log | |
else: | |
return None, log | |
run_btn.click(_run, inputs=[req_input, include_deps, prefix], outputs=[output_file, log_box]) | |
if __name__ == "__main__": | |
demo.launch() | |