""" 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()