TECHNOLOGY

FOR DJANGO PROJECT

PyScript を使ってみた

python で javascript みたいにフロントエンドをいじれます
github pages にもデプロイできるので、簡単なものなら無料で無限にアプリを作れます!

しかし読み込みがとても遅い...特に scikit-learn を読み込むととてつもない時間がかかってしまう...

だが!Pythonでフロントが書けてしまうWASMの進化に目が離せません!今後はサーバーを立てなくても簡単な機械学習ならフロント側で処理できる時代が来そうですね

初期設定

<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>

※ body の中に書き込む

<body>
    <py-config>
        # 必要なパッケージがあればこの中に書き込む...
    </py-config>

    <py-script>
        # この中に処理を書き込む...
    </py-script>
</body>

パッケージのインストール

<py-config>
    packages = ["matplotlib", "pandas"]
</py-config>

自作プラグインのインストール方法

下記のどちらでもいけたが、どちらが良いのかは不明

<py-config>
    [[fetch]]
    files = ["/request.py"]
</py-config>

または

<py-config>
    plugins = ["./request.py"]
</py-config>

実際の例

# 適当に作った関数
def test_func1(x):
    return x + 10
<py-config>
    plugins = ["./pyscript_test.py"]
</py-config>

<py-script>
    from pyscript_test import test_func1
    
    result = test_func1(23)
    display(result)
</py-script>

文字の表示

<py-script>
    def func1(n):
        display(f'{n}: Hello KEI!')

    for x in range(3):
        func1(x)
</py-script>

現在時刻を表示

<py-script>
    from datetime import datetime

    now = datetime.now()
    display(now.strftime("%m/%d/%Y, %H:%M:%S"))
</py-script>

要素の id を指定して書き込む

<div id="python_output" style="background-color: lightblue"></div>
<py-script>
    x = 500
    pyscript.write('python_output', x)
</py-script>

ボタンの操作受付

ボタンを押すと現在時刻を取得する例

<button py-click="current_time()" id="get-time" class="py-button">Get current time</button>
<p id="current-time"></p>

<py-script>
    from pyscript import Element
    import datetime

    def current_time():
        now = datetime.datetime.now()

        # 書き込む 要素のid を指定する
        paragraph = Element("current-time")

        # 現在時刻を指定した要素へ書き込む
        paragraph.write(now.strftime("%Y-%m-%d %H:%M:%S"))
</py-script>

ボタンを押すと Hello World を表示

<div id="manual-write"></div>
<button py-click="write_to_page()" id="manual">Say Hello</button>
<div id="display-write"></div>
<button py-click="display_to_div()" id="display">Say Things!</button>

<py-script>
def write_to_page():
    manual_div = Element("manual-write")
    manual_div.element.innerHTML = "<p><b>Hello World</b></p>"

def display_to_div():
    display("I display things!", target="display-write")
</py-script>

応用例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
</head>
<body>
    <form  onsubmit="return false">
        <label for="name">Name:</label><br>
        <input type="text" id="name" name="name" value="Ethan Hunt"><br>

        <select name="countries" id="countries">
            <option value="India">India</option>
            <option value="Germany">Germany</option>
            <option value="Netherlands">Netherlands</option>
        </select>

        <button py-click="sub()" type="submit" id="btn-form">submit</button>
    </form> 
 
    <p>Output:</p>
    <p id = 'output'></p>
    <py-script>
        def sub():
            result_place = Element('output')
            result_place.write(f"{Element('name').value} is a good human from {Element('countries').value}")
    </py-script>
    
</body>
</html>

非同期通信のやり方

<py-script>
    import asyncio
    
    async def main():
        for i in range(3):
            print(i)
            await asyncio.sleep(1)
    
    asyncio.ensure_future(main())
</py-script>

Fetch

まずは request.py を作成する。
pyodide というライブラリによって javascript の fetch を使用できるようになるらしい。
これによって GET, POST, PUT, DELETE を使用できるようになる。

from pyodide.http import pyfetch, FetchResponse
from typing import Optional, Any

async def request(url: str, method: str = "GET", body: Optional[str] = None,
                  headers: Optional[dict[str, str]] = None, **fetch_kwargs: Any) -> FetchResponse:
    """
    Async request function. Pass in Method and make sure to await!
    Parameters:
        url: str = URL to make request to
        method: str = {"GET", "POST", "PUT", "DELETE"} from `JavaScript` global fetch())
        body: str = body as json string. Example, body=json.dumps(my_dict)
        headers: dict[str, str] = header as dict, will be converted to string...
            Example, headers=json.dumps({"Content-Type": "application/json"})
        fetch_kwargs: Any = any other keyword arguments to pass to `pyfetch` (will be passed to `fetch`)
    Return:
        response: pyodide.http.FetchResponse = use with .status or await.json(), etc.
    """
    kwargs = {"method": method, "mode": "cors"}  # CORS: https://en.wikipedia.org/wiki/Cross-origin_resource_sharing
    if body and method not in ["GET", "HEAD"]:
        kwargs["body"] = body
    if headers:
        kwargs["headers"] = headers
    kwargs.update(fetch_kwargs)

    response = await pyfetch(url, **kwargs)
    return response

次に html 側で実装

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />

    <title>GET, POST, PUT, DELETE example</title>

    <link rel="icon" type="image/png" href="favicon.png" />
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />

    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    <py-config>
        [[fetch]]
        files = ["/request.py"]
    </py-config>
  </head>

  <body><p>
    Hello world request example! <br>
    Here is the output of your request:
    </p>
    <py-script>
        import asyncio
        import json
        from request import request  # import our request function.

        async def main():
            baseurl = "https://jsonplaceholder.typicode.com"

            # GET
            headers = {"Content-type": "application/json"}
            response = await request(f"{baseurl}/posts/2", method="GET", headers=headers)
            print(f"GET request=> status:{response.status}, json:{await response.json()}")

            # POST
            body = json.dumps({"title": "test_title", "body": "test body", "userId": 1})
            new_post = await request(f"{baseurl}/posts", body=body, method="POST", headers=headers)
            print(f"POST request=> status:{new_post.status}, json:{await new_post.json()}")

            # PUT
            body = json.dumps({"id": 1, "title": "test_title", "body": "test body", "userId": 2})
            new_post = await request(f"{baseurl}/posts/1", body=body, method="PUT", headers=headers)
            print(f"PUT request=> status:{new_post.status}, json:{await new_post.json()}")

            # DELETE
            new_post = await request(f"{baseurl}/posts/1", method="DELETE", headers=headers)
            print(f"DELETE request=> status:{new_post.status}, json:{await new_post.json()}")

        asyncio.ensure_future(main())
    </py-script>

    <div>
    <p>
        You can also use other methods. See fetch documentation: <br>
        https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters
    </p>
    </div>
    <div>
        <p>
        See pyodide documentation for what to do with a FetchResponse object: <br>
        https://pyodide.org/en/stable/usage/api/python-api.html#pyodide.http.FetchResponse
        </p>
    </div>
  </body>
</html>

>>Blog