pypi-to-ZIP / app.py
soiz1's picture
Create app.py
e55536e verified
"""
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()