FastAPI(Python3)でサーバを立ててMaya2020以前(Python2.7)からリクエストする

# はじめに
簡単にサーバが立てられちゃうFastAPIというライブラリを使って、Maya2020(Python2.7)からアクセスしてみました。

* メリット
    * サーバ側はPython3で作るので **Python3系のパッケージを扱える。** 
    * 何も追加インストールしていない **素のMaya2020以前(Python2.7)** でOK。
    * そもそもMayaの外側で動かすので **MayaのPythonバージョンに縛られない。**
* デメリット
    * 出来ることが限られる。Mayaに直接触れるようなものは書けない。（`maya.cmds`, UI関係など）
    * 必要な処理を事前に切り離して用意しておかないといけない。
    * サーバアプリを起動しておかなければならない。（Maya起動と連動させられれば特に気にならないかも）


## テスト環境
* Windows 10
* Maya 2020 (Python 2.7.11)
* Python 3.10.9

# サーバアプリを作る
まずFastAPIが使えるPython3.6以降の環境で`fastapi`と`uvicorn`をインストールします。
ここはまだMayaではなくVSCodeとかでやります。

```
// 仮想環境の作成
python -m venv venv
venv\Scripts\activate
python -m pip install -U pip

// パッケージインストール
pip install fastapi uvicorn
```

次にサーバを作成します。
```python:main.py
import os
from fastapi import FastAPI
from pydantic import BaseModel

import openai # ※これはサーバとは直接関係ないです

# APIリクエストに含まれるデータの構造と型を定義
class RequestData(BaseModel):
    model: str
    messages: list
    temperature: float = 1.0
    top_p: float = 1.0
    presence_penalty: float = 0.0
    frequency_penalty: float = 0.0

# FastAPIアプリケーションインスタンスを作成
app = FastAPI()

# POSTリクエストを受け付けるAPIエンドポイントを定義
@app.post("/openai/")
async def chat_completion(request_data: RequestData):
    """
    何らかのやりたい処理を書きます
    """

    # ※以下の例はOpenAI APIへリクエスト作成しレスポンスを取得するもの（別途 pip install openai してます）
    dict = request_data.dict()
    response = openai.ChatCompletion.create(
        model=dict["model"],
        temperature=dict.get("temperature", 1.0), 
        top_p=dict.get("top_p", 1.0), 
        presence_penalty=dict.get("presence_penalty", 0.0), 
        frequency_penalty=dict.get("frequency_penalty", 0.0), 
        messages=dict["messages"]
    )
    return response
```

コマンドラインで以下を入力しサーバを起動します。
```
uvicorn main:app --reload
```

こんな感じになれば成功です。`Ctrl+C`で終了させられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/09403217-4857-f480-1b3f-48eebdffbca2.png)

:::note
ちなみに、このくらいであればPyInstallerでexe化した1ファイルだけを別のPythonが入っていないPCに置いても正常に動作しました。

`main.py`に`uvicorn`が含まれるよう以下を書き加えます。
引数を追加していますが重要なのは最後の`uvicorn.run()`です。

```python:main.py (exe用 追記分)
import argparse
import uvicorn
import socket

"""（中略）"""

# コマンドライン引数のパーサー作成
parser = argparse.ArgumentParser()

# ローカルIPを取得するオプション追加
parser.add_argument(
    "-l", "--get_local_ip",
    action="store_true", 
    default=False,
)

# ポート番号を指定するオプション追加
parser.add_argument(
    "-p", "--port",
    type=int,
    default=8000,
)

opt = parser.parse_args()

# --get_local_ipが指定された場合はローカルIP取得、指定が無ければデフォルトの127.0.0.1
if opt.get_local_ip:
    hostname = socket.gethostname()
    host = socket.gethostbyname(hostname)
else:
    host = "127.0.0.1"

# 実行
uvicorn.run(app, host=host, port=opt.port)
```

`pyinstaller`でexe化します。
```
pip install pyinstaller
pyinstaller main.py --onefile
```

`dist`フォルダに`main.exe`が出来ていますのでダブルクリックで起動します。
LAN内にある別のPCに配置する場合は`-l`オプションをつけて起動します。
```
main -l
```
:::

# Maya2020からリクエストする
ようやくMayaさんの登場です。MayaはAPIリクエストのみを行います。
Maya2020には`requests`が入っていないので、`urllib2`を使っています。

```python:Maya 2020 ScriptEditor
import urllib2
import json

# リクエストデータ
data = {
    'model': 'gpt-3.5-turbo',
    'messages': [
        {'role':'system', 'content':u'語尾に「にゃ」を付けて答えてください。'},
        {'role':'user', 'content':u'日本の代表的な観光地を5つ教えて。'}
    ]
}

# リクエスト
req = urllib2.Request(
    'http://127.0.0.1:8000/openai/',
    headers={'Content-Type': 'application/json'},
    data=json.dumps(data)
)
response = urllib2.urlopen(req).read()

# 結果出力
response = json.loads(response)
print(response['choices'][0]['message'].get('content'))
```

```python:結果
東京タワー、京都の清水寺、富士山、広島の原爆ドーム、沖縄の首里城にゃ。
```

---

他に例を挙げたくて、Mayaの外部に切り離したいものって何かあるかな～と考えてみたのですが...

特に思いつきませんでした...ｗ 活用例お待ちしてます。