Project IDXでFastAPIベースのPanelアプリを作りたかったんだけど結局よく分からなかった
はじまり


※当ページはアフィリエイト広告を利用しています。
Project IDXとかいうクラウドIDEでWebアプリを作りたい
以前に存在を知った「Project IDX」という、Googleが開発中の、ブラウザ上もといクラウド上でUIテストとかも出来てしまうIDEを調べました。そして今回、それを実際に使ってみようというわけです。

「Project IDX」が気になった方は、公式のサイトはこちらです。2024-12-07時点ではWaitlist等は無く、すぐに開発を始めることが出来ます。
今回作りたいWebアプリ
今回は、PythonのWeb APIフレームワークであるFastAPIと、インタラクティブなダッシュボードをWeb上に作成できるライブラリであるPanelを使ってWebアプリを作っていきます。
GitHubにあるPanelのリポジトリからその様子を見てみると、Webページ上でなんかドラッグしたりすることが出来たりしそうなので、これをProject IDX上のプレビュー画面で実際にやってみたら便利だろうなあっと思って、まずはProject IDXでその両者のパッケージを動かせる状態にするところから始めていきたいと思います。
そして、Panel公式のページの中に、FastAPIを使ってPanelのダッシュボードを作るチュートリアル的なものがあるので、そのコードを試してみます。
結論:環境の作り方がよく分からなかった。
あーーー、くそう・・・。結局はベータ版ってことかぁ・・・??
Project IDXでは現在(2024-12-07時点)では、公式から提供されているPythonのフレームワークのテンプレートは、「Python Flask」と「Python Django」の2種類しか置いてありません。
なので、FastAPI + Panelのテンプレートは自分で用意する必要がありますが・・・、これがなんともまあ訳が分からなかった。 結局、どこをどう設定すればPanelアプリをプレビューすることが出来るのかが分からなかったんですよね。
ですがしかし、その奮闘する過程で奇跡的に1つのワークスペースだけ、FastAPIベースのPanelアプリを開発することに成功した環境がありました。実際に開発して画面の中のUIを動かすことも出来たので、この謎が極まる複雑怪奇な環境構築さえ出来てしまえば、快適なWebアプリ開発を恍惚として堪能できた筈なんですよね。
これが実際にProject IDX上で、FastAPIで提供したAPIパスから色々なダッシュボードを操作している映像です。

エディタ上でレンダリングされたWebページの反応速度は悪くありません。インタラクティブにウィジェットを操作することが出来ています。

しかし結局、このプレビューを再現することは叶いませんでした・・・。

このクラウドIDEはベータ版・・・。そうは問屋が卸してくれませんでした・・・。
一応、今回動かしたPythonのソースを貼っておきます。
app.py:
from src import panel
from fastapi import FastAPIfrom panel.io.fastapi import add_applicationsfrom fastapi.middleware.cors import CORSMiddleware
import uvicornimport os
app = FastAPI()
origins = ["*"]app.add_middleware( CORSMiddleware, allow_origins=origins, # List of allowed origins allow_credentials=True, allow_methods=["*"], # Allow all methods allow_headers=["*"], # Allow all headers)
@app.get("/")async def read_root(): return {"Hello": "World"}
def create_panel_app(): return panel.create_slider(10, 3, "⭐")
add_applications({ "/panel_app1": create_panel_app , "/panel_app2": panel.create_column("I am a Panel object!") , "/panel_app3": "my_panel_app.py" , "/panel_app4": panel.create_figure_linking_slider() , "/panel_app5": panel.create_tabs() , "/panel_app6": panel.create_data_frame(400) , "/panel_app7": panel.create_text_input("Input file path.") , "/panel_app8": panel.create_button("Read the specified file") , "/panel_app9": panel.create_other_widgets() , "/panel_app10": panel.create_big_tabs() , "/panel_app11": panel.create_sine_wave() , "/panel_app12": panel.create_figure_linking_slider()}, app=app)
if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))src/panel.py:
import panel as pnimport numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport matplotlib.figureimport datetime as dtfrom bokeh.models import CustomJS, Slider
pn.extension(comms="vscode") # to use on VSCode# pn.extension(comms="bokeh") # to use on Bokeh
# slider = pn.widgets.FloatSlider(name="Slider", start=0, end=10, value=5)
def create_slider(end: int, default_value: int, gauge_icon: str = "⭐") -> pn.widgets.IntSlider: slider = pn.widgets.IntSlider(name='Slider', start=0, end=end, value=default_value) return slider.rx() * gauge_icon
slider = pn.widgets.FloatSlider(name="Slider", start=0, end=10, value=5)@pn.depends(slider.param.value)def plot_sine_curve(value) -> matplotlib.figure: fig, ax = plt.subplots() x = np.linspace(0, 10, 100) y = np.sin(x + value) ax.plot(x, y) plt.close(fig) # Prevent from memory leak. return fig
def create_figure_linking_slider() -> pn.Column: return pn.Column(slider, plot_sine_curve).servable()
def create_column(title: str) -> pn.Column: return pn.Column(title)
def create_tabs() -> pn.Column: tab1 = pn.Column("This is tab 1") tab2 = pn.Column("This is tab 2") tabs = pn.Tabs(("Tab 1", tab1), ("Tab 2", tab2)) return tabs.servable()
def create_big_tabs() -> pn.Column: tab1 = create_other_widgets() tab2 = create_other_widgets() tabs = pn.Tabs(("Tab 11", tab1), ("Tab 12", tab2)) return tabs.servable()
def create_data_frame(width: int) -> pn.widgets.DataFrame: data = {"col1": [1, 2, 3], "col2": [4, 5, 6]} df = pd.DataFrame(data) table = pn.widgets.DataFrame(df, width=width) return table.servable()
def create_text_input(title: str, placeholder: str = "Input me."): return pn.widgets.TextInput(name=title, placeholder=placeholder)
def create_button(title: str, button_type: str = "primary"): return pn.widgets.Button(name=title, button_type=button_type)
def create_other_widgets(): # 2. チェックボックスタグルグループ checkbutton_group = pn.widgets.CheckButtonGroup( name="Checkbox Group" , value=["Apple", "Pear"] , options=["Apple", "Banana", "Pear", "Strawberry"] , orientation="horizontal" ) # 2. チェックボックスタグルグループ checkbox_group = pn.widgets.CheckBoxGroup( name="Checkbox Group" , value=["Apple", "Pear"] , options=["Apple", "Banana", "Pear", "Strawberry"] , inline=True ) # 3. ラジオボタングループ radio_group = pn.widgets.RadioButtonGroup( name="Radio Button Group" , options=["Biology", "Chemistry", "Physics"] , button_type="success" ) multi_choice = pn.widgets.MultiChoice( name="MultiChoice" , value=["Apple", "Pear"] , options=["Apple", "Banana", "Pear", "Strawberry", "melon", "dragon fruit", "orange", "grape", "muscat", "avocado", "peach", "tomato"] ) # 5. マルチセレクト multi_select = pn.widgets.MultiSelect( name="MultiSelect" , value=["Apple", "Pear"] , options=["Apple", "Banana", "Pear", "Strawberry"] , size=8 ) # 6. トグルボタン toggle_group = pn.widgets.ToggleGroup( name="ToggleGroup" , options=["Biology", "Chemistry", "Physics"] ) # 7. セレクター (様々なウィジェットのラッパー) selector = pn.widgets.Select( name="Select" , options=["Biology", "Chemistry", "Physics"] ) # 8. カラーピッカー color_picker = pn.widgets.ColorPicker( name="Color Picker" , value="#99ef78" ) # 9. 日付ピッカー date_picker = pn.widgets.DatePicker( name="Date Picker" , value=dt.datetime(2024, 4, 1, 11, 37) ) # 10. 静的なテキスト (既に使用済み) data_slider = pn.widgets.DateSlider( name="Date Slider" , start=dt.datetime(2019, 1, 1) , end=dt.datetime(2019, 6, 1) , value=dt.datetime(2019, 2, 8) ) # 11. text editor text_editor = pn.widgets.TextEditor(placeholder="Enter some text") # 12. json editor spinner = pn.widgets.JSONEditor(value={ "dict" : {"key": "value"}, "float" : 3.14, "int" : 1, "list" : [1, 2, 3], "string": "A string", }, width=400) # 13. ファイル入力 file_input = pn.widgets.FileInput(name="File Input") # 14. リテラル入力 (コード入力) literal_input = pn.widgets.LiteralInput( name="Literal Input (dict)" , value={"key": [1, 2, 3]} , type=dict ) # 15. パスワード入力 password_input = pn.widgets.PasswordInput( name="Password" , placeholder="Enter your password here..." ) # 16. テキストエリア入力 textarea_input = pn.widgets.TextAreaInput( name="Text Area Input" , placeholder="Enter a string here..." ) # 17. Switch switch = pn.widgets.Switch(name="Switch") # 18. file dropper file_dropper = pn.widgets.FileDropper()
player = pn.widgets.Player( name="Player" , start=0 , end=100 , value=32 , loop_policy="loop" , show_value=True , value_align="start" )
autocomplete = pn.widgets.AutocompleteInput( name="Autocomplete Input" , options=["Biology", "Chemistry", "Physics"] , case_sensitive=False , search_strategy="includes" , placeholder="Write something here" ) # 全てのウィジェットをカラムに配置 return pn.Column( checkbutton_group , checkbox_group , radio_group , multi_choice , multi_select , toggle_group , selector , color_picker , date_picker , data_slider , text_editor , spinner , file_input , literal_input , password_input , textarea_input , switch , file_dropper , player , autocomplete , create_slider(10, 5) , create_figure_linking_slider() , create_column("Column Title") , create_tabs() , create_data_frame(400) , create_text_input("Text Input") , create_button("Button") ).servable()
def create_sine_wave(): # Create the initial plot x = np.linspace(0, 10, 500) y = np.sin(2 * np.pi * x)
fig, ax = plt.subplots() line, = ax.plot(x, y) ax.set_xlabel("x") ax.set_ylabel("sin(2πx)") ax.set_title("Sine Wave")
# Create sliders for frequency and amplitude frequency_slider = Slider(start=0.1, end=5, value=1, step=.1, title="Frequency") amplitude_slider = Slider(start=0.1, end=5, value=1, step=.1, title="Amplitude")
# Define the update function (using CustomJS for interactivity, better for larger datasets than pn.bind) code = """ const data = source.data; const f = cb_obj.value; const a = amplitude_slider.value; const x = data['x']; const y = data['y']; for (var i = 0; i < x.length; i++) { y[i] = a * Math.sin(2 * Math.PI * f * x[i]); } source.change.emit(); """
# Convert the matplotlib figure to a bokeh figure (required for interaction within panel) from bokeh.io import show from bokeh.plotting import figure from bokeh.models import ColumnDataSource
source = ColumnDataSource(data=dict(x=x, y=y))
p = figure(width=400, height=400, tools="crosshair,pan,wheel_zoom,box_zoom,reset") p.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
# Attach the callback to the sliders callback = CustomJS(args={'source': source, 'frequency_slider': frequency_slider, 'amplitude_slider': amplitude_slider}, code=code) frequency_slider.js_on_change('value', callback) amplitude_slider.js_on_change('value', callback)
# Arrange the elements in a Panel layout dashboard = pn.Column( pn.Row(frequency_slider, amplitude_slider), p # Use the Bokeh figure directly )
return dashboard.servable()苦戦したついでにその過程を書き連ねます
同じような野望を持って、Panelアプリを再現しようと思った後世の方々に、環境を構成するために試してみた事柄を書き連ねていきたいと思います。
dev.nixの構成
.idx/dev.nixというファイルで構成する。
まず、Project IDXでワークスペースを作成して、そのためのVMが起動すると、とある構成ファイルを元に環境が構築されていきます。それが.idx/dev.nixファイルです。「NIX」という記法で書いていきます。yamlとかjsonみたいな感じの拡張子です。
その構成ファイルの作り方のリファレンスは、公式のものが載っています。
そして、その構成ファイルは主に4つのフィールドに分かれています。
channel・・・ワークスペースのバージョン的なもの。2024-12-04時点では、stable-23.05、stable-23.11、stable-24.05、unstableのいずれかです。(stable-24.11を出して欲しい・・・。)packages・・・ワークスペースにインストールしたいパッケージ情報。リスト形式。env・・・設定したい環境変数を key-value ライクにリスト形式で書きます。idx・・・ワークスペースを作った後にどんな処理をさせるかを書きます。
package
packageフィールドには、Pythonを入れたりpipを入れたりしていきます。はたまた、FastAPIを入れたりPanelを入れたりすることも出来るわけですが、ここのフィールドでそのライブラリとかまで指定する必要があるのかどうかは現在自分は解明できていません。
Pakcageの識別子は、以下のNixOSのパッケージリストの一覧から調べて書いていきます。python311Packages.pipやらpython311Packages.uvicornやらが識別として存在します。
ちなみに、「NixOS」という文字が見えたので、そーいうOS上で動いているのかと思ったのですが、それは違うらしいです。
このProject IDXというIDEは、「NixOS」というシステムを組み込んでいるわけではなく、①広く利用されていて、②キャッシュが効いて速い、「Nixパッケージ」のシステムを取り入れているということらしいです。つまり、みんなが使いやすくてスピーディに開発できるように「Nix」を採用したっぽいですね。(そうか、NIXはNginx用のファイルを作る時にも使うのか。)

idx
envは飛ばしておいて、次に鬼門となるidxフィールドです。
このidxフィールドが最も設定する部分が広いです。開発していくWebアプリをプレビューするのかどうかもここに書いていきます。
idxフィールドでは主に4種類にフィールドが分かれています。
extensions・・・IDEで予めインストールしたい拡張機能をリスト形式で書きます。workspace・・・ワークスペースを作ったり開いた時に実行したいことを書きます。previews・・・・プレビューを表示するための事柄を書きます。
extensionsフィールドでは、拡張機能のパッケージの識別子を完全修飾された拡張機能 ID のリストで書いていきます。${publisherId}.${extensionId}みたいな感じです。
エディタの拡張機能の部分で探したり、Open VSX Registryから探して記載しています。
workspaceフィールドでは、onCreateとonStartの2種類のトリガーで行う処理(Bashコマンド)を記述していきます。
onCreateはワークスペースが作られる時に参照されるフィールドです。なので、ワークスペースをリビルドする時にはこのフィールドで宣言されているinstallの内容は実行されません。なので、ビルドの仕様がまだ決まっていない時は、onStartのフィールドの方のinstallに実行内容を書く方が良さそうです。(ビルドする内容が固まったら、onCreateだけに記載して、ワークスペースを再開する時にいちいちコマンドが実行されないようにしても良さそう。)
そして、previewフィールドで、アプリのプレビューをしていくかどうかを決めることが出来ます。enable = true;と書けばプレビューできるようになります。しかし、そのenableフィールドの隣にさらにpreviewsフィールドがあって、(idx.preview.previewということです。)その中でプレビューするためのコマンドと、ポート番号などを書いていきます。
Python Flaskアプリを参考に設定していく。
それでは、公式テンプレートのPython Flaskアプリから、構成ファイルをまねていきたいと思います。
Python Flaskテンプレートでは、このような構成になっています。
# To learn more about how to use Nix to configure your environment# see: https://developers.google.com/idx/guides/customize-idx-env{ pkgs, ... }: { # Which nixpkgs channel to use. channel = "stable-24.05"; # or "unstable" # Use https://search.nixos.org/packages to find packages packages = [ pkgs.python3 ]; idx = { # Search for the extensions you want on https://open-vsx.org/ and use "publisher.id" extensions = [ "ms-python.python" ]; workspace = { # Runs when a workspace is first created with this `dev.nix` file onCreate = { install = "python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt"; # Open editors for the following files by default, if they exist: default.openFiles = [ "README.md" "src/index.html" "main.py" ]; }; # To run something each time the workspace is (re)started, use the `onStart` hook }; # Enable previews and customize configuration previews = { enable = true; previews = { web = { command = [ "./devserver.sh" ]; env = { PORT = "$PORT"; }; manager = "web"; }; }; }; };}channelは最新版のstable-24.05ですね。そして、idxフィールドでは、workspace.onCreateとpreviewsが目立ちます。onCreate.installで、Pythonの仮想環境を作ってその中にpip installしていますね。そして、default.openFilesではワークスペースを作成した時に予め最初に開いておくファイルが書いてあります。onCreate.installの部分は拝借しておきましょう。
そして、previews.previewsでは、./devserver.shというものを使ってプレビューを再現しているようです。その中身を見てみましょう。
#!/bin/shsource .venv/bin/activatepython -m flask --app main run -p $PORT --debugvenvの中に入って、Flaskを起動するようになっていますね。この部分をFastAPIを起動するように編集すれば良さそうです。
最終的なFastAPI用のdev.nix
これが最終的な.idx/dev.nixです。少々長いですが、何が必要なのかどうかが分かっていないので冗長に書いています。
# To learn more about how to use Nix to configure your environment# see: https://developers.google.com/idx/guides/customize-idx-env{ pkgs, ... }: { channel = "stable-24.05"; # "stable-23.05" or "unstable" # Use https://search.nixos.org/packages to find packages packages = [ pkgs.python3 pkgs.python311Packages.pip pkgs.google-cloud-sdk pkgs.python311Packages.fastapi pkgs.python311Packages.uvicorn pkgs.python311Packages.panel ]; # Sets environment variables in the workspace env = { };
idx = { # search for the extension on https://open-vsx.org/ and use "publisher.id" extensions = [ "ms-python.python" "rangav.vscode-thunder-client" ]; workspace = { # runs when a workspace is first created with this `dev.nix` file # to run something each time the environment is rebuilt, use the `onStart` hook onCreate = { create-venv = '' python -m venv .venv source .venv/bin/activate pip install --no-cache-dir -r requirements/dev.txt ''; }; onStart = { # install = '' # python -m venv .venv # source .venv/bin/activate # pip install --no-cache-dir -r requirements/dev.txt # ''; # Open editors for the following files by default, if they exist: default.openFiles = [ "README.md" "app.py"]; # default.openFiles = [ "README.md" ]; # run-server = "./devserver.sh"; # Example: start a background task to watch and re-build backend code # watch-backend = "npm run watch-backend"; };
}; # preview configuration, identical to monospace.json previews = { enable = true; previews = [ { command = [ "./devserver.sh" ]; env = { PORT = "$PORT"; }; id = "web"; manager = "web"; } ]; }; # previews = { # enable = true; # previews = [ # { # command = [ # "./devserver.sh" # ]; # env = { # PORT = "$PORT"; # }; # id = "web"; # manager = "web"; # } # ]; # } };}requirements.txtの中身
現時点で最も怪しいと思っている、requirements.txtの中身です。基本的には、後述のFastAPIをProject IDXで構成する同志の方々のものを真似ていますが、これでPanelのプレビュー画面を再現するまでには至っていません・・・。
annotated-types==0.7.0anyio==4.7.0autopep8==2.0.4click==8.1.7exceptiongroup==1.2.0fastapi==0.115.6h11==0.14.0idna==3.10pycodestyle==2.11.1pydantic==2.10.3pydantic_core==2.27.1sniffio==1.3.1starlette==0.41.3tomli==2.0.1typing_extensions==4.12.2uvicorn>=0.32.1# Panelpanel[fastapi]>=1.5.2# Google Cloudfunctions_framework>=3.5.0# Cloud SQLcloud-sql-python-connector[pg8000]sqlalchemy>=2.0.36パッケージ達の概要としてはこんな感じです。HTTP関連のヤツらまでは必須だと思います。
コアとなるパッケージ:
- fastapi==0.105.0: 高速でモダンなWeb APIフレームワーク。Pythonの型ヒントを利用して、APIの定義、検証、ドキュメント生成を効率的に行えます。
- uvicorn==0.24.0.post1: ASGI(Asynchronous Server Gateway Interface)サーバー。FastAPIアプリケーションを実行するために使用されます。高速で軽量なサーバーとして知られています。
- starlette==0.27.0: ASGIツールキットおよびフレームワーク。FastAPIはStarletteをベースに構築されています。ルーティング、ミドルウェア、WebSocketなどの機能を提供します。
- pydantic==2.5.2: データ検証と設定管理ライブラリ。Pythonの型ヒントを使用して、データの型、制約、変換などを定義できます。FastAPI は Pydantic を使用してリクエストボディとレスポンスボディを検証します。
- pydantic_core==2.14.5: Pydantic 2 のコアバリデーションロジックを提供するライブラリ。パフォーマンス向上のため分離されています。
非同期処理関連:
- anyio==3.7.1: 非同期プログラミングのためのライブラリ。異なる非同期イベントループ実装(
asyncio,trio)を抽象化し、移植性の高いコードを書くことができます。 - sniffio==1.3.0: 実行時にどの非同期ライブラリ(
asyncio,trio)が使用されているかを検出するライブラリ。anyioのようなライブラリで使用されます。
HTTP関連:
- h11==0.14.0: HTTP/1.1プロトコルを実装した低レベルライブラリ。
uvicornなどのASGIサーバーで使用されます。 - idna==3.6: 国際化ドメイン名(IDNA)を処理するためのライブラリ。URLパーサーなどで使用されます。
型ヒント関連:
- typing_extensions==4.9.0: Pythonの標準ライブラリのtypingモジュールに含まれていない型ヒントを提供するライブラリ。新しいPythonバージョンで導入された型ヒントを古いバージョンで使用する場合などに利用されます。
- annotated-types==0.6.0: 型ヒントにアノテーションを追加するためのライブラリ。より詳細な型情報を提供できます。
ユーティリティ:
- click==8.1.7: コマンドラインインターフェース(CLI)を作成するためのライブラリ。FastAPIアプリケーションでCLIコマンドを定義するために使用できます。
- tomli==2.0.1: TOMLファイルのパーサー。設定ファイルの読み込みなどに使用できます。
コードフォーマット/静的解析:
- autopep8==2.0.4: PEP 8スタイルガイドに従ってPythonコードを自動的にフォーマットするツール。
- pycodestyle==2.11.1: PEP 8スタイルガイドに準拠しているかをチェックする静的解析ツール。
構成時の引っかかりポイント
結局引っかかったままでしたが、途中で遭遇した引っかかりポイントをまとめます。
1. VSX(拡張機能)の再インストール
dev.nixで言うところの、idx.extensionsのフィールドに当たります。この拡張機能のインストール処理は、まあよく分かりません。拡張機能がインストールされたら、エディタ上にインストールされた旨が表示されるものですが、不思議なことに表示されません。なので、実際にはインストールされているのにインストールされていないことになっている。(もしくはインストールされていないのにインストールされていることになっている。)
このアベコベ状態を解消するのは、Project IDXの開発陣の方々にお任せして、使う側としてまず覚えておく必要があることは、idx.extensionsで指定した拡張機能は「ワークスペースを作った時にだけ指定すれば良い。」ということです。
まあ、このdev.nixという構成ファイルはワークスペースを作って開いた後も編集して、ワークスペースを「リビルド」することが出来るのですが、その際にビルド時に元々書いてあったidx.extensionsのフィールドにある拡張子はコメントアウトしましょう。そうしないと、dev.nix内の処理がつっかえてしまいます。installatoin failed. already installed.みたいなエラーログだったり、こんなエラーログと共に。
Installing extensions… Updating the extension ‘ms-python.python’ to the version 2024.20.0 Installing extension ‘ms-python.python’ v2024.20.0… Error while installing extension ms-python.python: Can’t install ‘ms-python.python’ extension because it is not compatible with the current version of Code OSS for Cloud Workstations (version 1.89.1). Failed Installing Extensions: ms-python.python
特にバージョン指定をしなくても、ただエディタ上でPythonを使いたいだけなので、とりあえず一度Pythonを入れてしまって、リビルドの際にはコメントアウトです。他の拡張機能も同様ですね。
2. どのパッケージを参照しているんだか分からない。
requirements.txtのパッケージを参照してソースが動いているんだか、packagesフィールドで指定したパッケージを参照して動いているんだかよく分からないんですよね。まあでも、Python Flaskテンプレートを見たら、PythonをNixパッケージで入れたら、あとはpip install -r requirements.txtか・・・。
しかし、後述のFastAPIをProject IDXで構成する同志の方々はpkgs.python311Packages.pipをパッケージの中に入れている・・・。うーむ、解せん。まだよく分かっていません。
3. プレビュー画面が表示されない。(その1:バージョンが古い)
最大の峠がここでした。未だに越えきれていません。
FastAPIをProject IDXで構成する同志の方々のdev.nixを拝見させていただいて、FastAPIをプレビューできる構成ファイルを見つけたのでここに共有しておきます。
この構成ファイルは、 channel = "stable-23.05"; で少し古いのですが、これでビルドするとFastAPIのページをプレビューすることが出来るようになります。これにはかなり助かりました。
しかし、このdev.nixのままでは、FastAPIでPanelを表示するページのプレビューは表示できませんでした。なので、その際に色々奮闘して、奇跡的に成功した dev.nix では、 channel = "stable-24.05"; になっていたので、おそらくそのバージョンでないとPanelの画面はプレビュー出来ないのでは無いのかと思っています。
それでは、ここから長い長いプレビュー画面を探求する旅路の記録へと参ります。
4. プレビュー画面が表示されない。(その2:Try again実行が早い。)
プレビュー表示されなくなるというのは、 Error starting preview が表示される状態を指します。この状態は、 dev.nix の実行中でエラーが発生しています。なので、 dev.nix もしくはそのファイルで参照しているファイルを直してから Try again をすると再実行されます。修正部分が正しかったら、ちゃんとプレビューが表示されるようになります。

Error starting preview が表示される原因として、pipなどによるパッケージがまだインストールが完了していないことが考えられます。その場合は、インストールが完了するまで待ちましょう。と言っても、いつ完了するのかを確認する方法が分からないので、1分ぐらい待てば確実でしょうか。
奇跡的に成功した環境で、 Error starting preview が表示された際には、ワークスペースが開いてから少し待っていたら開いたというわけです。
5. プレビュー画面が表示されない。(その3:「Try again」を実行したかどうか。)
これは先程とは逆のパターンですね。Error starting previewの文字を見た瞬間にすぐさまリビルドしてしまうパターン。
Try againを実行すると、No Module Namedが表示されたりする時もありますが、待機していると無事正常にレンダリングされるようになったりします。Try againしてから30秒は待ち続けた方が良いと再度記しておきます。
6. プレビュー画面が表示されない。(その4:「pycache」や「.venv」があるかどうか。)
Pythonでpipとかでパッケージをインストールするとディレクトリの中に出てきますよね、__pycache__や.venv。
それらがあると、自分が今設定した内容が果たして本当に思い描いている環境を再現する至っているのか、それが分からなくなります。
なので、リビルドする際には、__pycache__や.venvは消しておいた方が良い。環境差異が発生しにくくなります。
7. プレビュー画面が表示されない。(その5:「リビルド=ゼロからやり直す」わけではない。)
これは、コンテナをいつも使っていたりすると引っかかりそうですが・・・。(僕が沼った最大の原因はここかもしれません。)
Project IDXのワークスペースをリビルドすることは、環境をゼロから作り直しているわけではなく、加筆しているような感覚です。つまり、リビルドした後に上手く環境を構築できたと思ってその瞬間のファイルをメモしたら、そのリビルドの前にやったファイルの内容もメモっておく必要があったりするわけです。
なので、リビルドする前にrequirements.txtの中身を消したりしていると、その中身がビルドに必要なものだったりする可能性もあるわけです。僕は、この点を理解していなくて、奇跡的に成功したワークスペースで一体どういう構成ファイルを使ってリビルドしてきたかどうかを忘れてしまったのでした。そして未だに再現できていない・・・。
そのため、新しい環境のビルドを試している間は、自分がビルドに使ったパッケージ等はしっかりメモしておくことが必要です。コンテナを読み込み直したり、VMを再起動しているわけではないんですねえ。(もしくは、Project IDXのHome画面に戻って、ワークスペースの横にある横三点リーダーをクリックしてResetで再起動することができます。その分だけ時間が掛かるんですけどね・・・。)
9. Panelのページのプレビュー画面に出てくるエラー出力が貧弱すぎる。
FastAPIの表示に上手くいくと、JSONは間違いなくちゃんと返してくれるようになります。

そんな感じでFastAPIの表示には上手くいくものの、Panelの表示が出来ない時、Internal Server Errorとだけ表示されます。Internal Server Errorとしか表示されません。

ブラウザを使って中身を見れるわけでも無いので、もうお手上げです。奇跡的に成功したワークスペースを参考に色々とパッケージを揃えていくものの、ずーっと、Internal Server Errorとだけ表示されます。
まとめ
今回は、Project IDXというクラウドIDEでPanelライブラリとFastAPIフレームワークによるWebアプリの開発で挫折した記事でした。
以下、本記事のまとめです。
- Project IDXのワークスペースは、
.idx/dev.nixというファイルで構成していく。 idx.workspaceのonCreateの時などに、既に拡張機能をインストールしていたら、onStart時にはインストールする必要はない。むしろエラーの原因になるので、既に入っている拡張機能はコメントアウトしておく。- ワークスペースが開いて、
Error starting previewが表示されたら、Try againをクリックして30秒ぐらい待っておくと、ちゃんとプレビューが開くことがある。 __pycache__や.venvによって、プレビューが開くかどうか左右されたりもする。- 「リビルド=ゼロからやり直す」わけではない。
Project IDXに関する情報は、以下の公式ページでも確認することが出来ます。
なんか公式のトラブルシューティング関連のページもありました。
Project IDXのコミュニティはここにあります。ここでもIDXを使っている開発者達が互いにトラブルシューティングし合っています。

今回のProject IDXの構成の話って、もしかしてNginxを分かっていないとムズカシイ・・・? くそう、やったことないぞ・・・。(今度NASを組むから齧るかもしれないが。)
クラウドIDE関連記事
その他のクラウドIDE関連の記事を貼っておきます。やっぱり当分はコイツで開発することにします。

Nginx関連の書籍
おしまい


以上になります!
記事を共有
この記事が役に立ったなら、ぜひ他の人と共有してください!