よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【Windows10とAndroid】Pythonとbottleで自作するJupyter風ナイスなポンコツWebアプリ

PythonとWebフレームワークbottleを使って、JupyterNotebook風Webアプリを自作した。その記録。
前々回に作ったものを改造。
前回
前々回

なんと、ブラウザ上でPythonコードが実行できます!


htmlを書き換えれば独自のJupyterが作れる。



実行風景


起動画面

f:id:chayarokurokuro:20191107213526j:plain


Pythonコードを入力中

f:id:chayarokurokuro:20191107213555j:plain


実行ボタンを押すと…

f:id:chayarokurokuro:20191107213616j:plain


カレンダー表示コマンドをPythonから…

f:id:chayarokurokuro:20191107213648j:plain


カレンダーが表示された

f:id:chayarokurokuro:20191107213720j:plain


グラフをファイル出力するコードを…

f:id:chayarokurokuro:20191107213739j:plain


実行しても画面上に表示なし

f:id:chayarokurokuro:20191107213817j:plain


ちゃんと画像ファイル保存されていた

f:id:chayarokurokuro:20191107213836j:plain


エラーが出た場合はこんな感じ

f:id:chayarokurokuro:20191107213938j:plain


f:id:chayarokurokuro:20191107213947j:plain



実行環境


Androidスマホ
termux
Python3.7
ブラウザGoogleChrome



ブラウザを起動させる部分でtermuxがインストールされた前提のAndroidWindows用に作っています。前々回のちょっと改造しただけなのでWindows10でも動くと思うが未確認。



目次




ライブラリ


  • bottle
  • os
  • webbrowser
  • subprocess


bottleはpip install等でインストールが必要です。



作るもの


  1. プロジェクトフォルダ(作るファイルの入れ物)
  2. index.html(入力・出力)
  3. main.py(実行用Pythonファイル)


(1)を適当な場所に作ったら、中に(2)(3)を入れておきます。

bottleno module の場合は、bottleの公式サイトから bottle.py をダウンロードするかコードをその名前で保存し、(1)の中に一緒に入れておけば大丈夫です。



実行方法と動作


実行方法
プロジェクトフォルダをカレントディレクトリにしてターミナルから

python main.py


動作の流れ

  1. 実行
  2. 入力保存用フォルダ自動作成
  3. ブラウザ自動起動しindex.html表示
  4. 入力したら実行ボタンを押す
  5. フォーム下に標準出力を表示される
  6. 入力内容は一旦ファイルに上書きで書き出される
  7. subprocess.PopenでPythonにそのファイルを実行させる
  8. stdout,stderrをbottleのテンプレートに渡して
  9. 実行結果をブラウザ表示



機能
いうなれば、Pythonしか実行できない単一のcodeセルです。



(1)プロジェクトフォルダ作成


適当にフォルダを作ります。

(2)index.html作成


フォルダ(1)に保存

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <title>Python実行アプリ</title>

</head>

<body>
    <form action="/hello" method="post" accept-charset="UTF-8">

        <p>【python code】<br><p><input type="reset" value="Reset"></p>
            <textarea name="example1" id="example" cols="40" rows="8" wrap=off style="overflow:auto;"></textarea>

        <p><input type="submit" value="実行"></p>
        <pre><p>{{text0}}</p></pre>
        <pre><p>{{text1}}</p></pre>

    </form>

</body>
</html>



(3)main.py作成


これも同じフォルダに保存

from bottle import route,template,run,request
import os
import webbrowser
import subprocess


# 投稿保存用フォルダ作成
def make_log_folder():
    os.makedirs("log_folder", exist_ok=True)

# ブラウザ起動
def open_browser():
    #url
    url = "http://localhost:8080/hello"
    # osの種類を取得
    which_os_name = os.name

    # osブラウザ起動
    if which_os_name == "nt": # windows
        webbrowser.open(url)
    elif which_os_name == "posix": # androidでtermuxをインストール済
        cmd = "termux-open-url " + url
        subprocess.run(cmd.split())


# bottle API
@route('/hello',method=["GET", "POST"])
def index():
    # textを取得
    text = request.forms.example1
    with open("log_folder/log.py","w") as f:
        f.writelines(text)
    # コマンド実行    
    python_file = "log_folder/log.py"
    cmd = "python " + python_file
    proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    
    # 正常とエラーの標準出力取り出し
    result = proc.communicate()
    res0 = result[0] # ok
    res1 = result[1] # error

    if isinstance(res0,bytes):
        res0 = res0.decode("utf-8")

    return template('index.html',text0=res0,text1=res1)


# 実行
make_log_folder()
open_browser()
run(host='localhost',port=8080,reloader=False,debug=True)



備考


index.html
前回、前々回と基本的に変えていない。textareaの入力フォームとinputボタン。今回はそこにリセットボタンを付けた。
<input type="reset" value="Reset"></p>を書いておけば、ボタンを押したときに勝手にtextareaの入力をクリアしてくれる。

{{text0}}は正常に実行された時の標準出力が、{{text1}}はエラー時の出力が渡されてレンダリング、置き換わります。



main.py
カレントディレクトリにフォルダ(./log_folder)を作成し、フォーム入力内容を上書き保存で書き込んだファイル(log.py)をそこへ保存する。
subprocess.Popen()でpython ファイル名を実行させるが、そのファイルパスが参照される。

result = proc.communicate()で正常とエラーの標準出力がタプルで取り出せる。
あとはreturnでテンプレートに渡してやる。

ブラクラ注意!


ブラウザ起動とreloader=Trueの組み合わせ

コードをいじっている最中、ブラウザを大量に起動させてコンピュータをクラッシュさせる通称ブラクラが発生した。

main.pyにブラウザを起動させるコードをつけているが、もしreloaderTrueにしておくと何等かの理由でbottleのサーバがストップしたりするたびにrun()関数が何度も呼ばれ、その都度ブラウザが新しいタブを開く。ブラウザクラッシャー発生w

クラッシュしない仕組みを組み込んでおかないといけませんですね。プロはどうしてる( -_・)?だろう?

reloader=Falseで!



以上です。