원문 캡처 · blog.holoviz.org
원문 캡처 · blog.holoviz.org

Panel 1.10.0은 Material UI 컴포넌트를 panel.ui로 통합하고, FastAPI·Django와 연결할 수 있는 자체 ASGI 서버를 추가했다. 기존 앱을 옮기는 panel migrate 명령과 컴포넌트의 자바스크립트 자원을 필요할 때 불러오는 기능도 포함한다.

panel.ui는 Material UI 컴포넌트와 Material UI 대응 항목이 없는 기존 Panel 컴포넌트를 한 네임스페이스에서 제공한다. 기존 API와 디자인은 그대로 유지되며, 새 네임스페이스는 선택해 사용할 수 있다. Material과 기존 컴포넌트를 한 앱에서 섞어 쓸 수 있어 단계적으로 옮길 수 있다. panel migrate는 가져오기 경로와 일부 인자, 호환되는 템플릿을 바꾸며, 안전하게 변환할 수 없는 항목은 수동 검토 대상으로 남긴다. CSS와 사용자 정의 디자인 등은 직접 수정해야 한다.

panel serve는 tornado를 기본 서버로 유지하면서 asgi와 fastapi 선택지를 추가했다. ASGI 서버에서는 Tornado 요청 핸들러로 구현된 --rest-provider, --enable-xsrf-cookies, --num-procs 옵션을 사용할 수 없으며, 해당 옵션을 지정하면 오류가 발생한다. FastAPI·Django 통합은 더 이상 bokeh-fastapi와 bokeh-django에 의존하지 않는다. pn.extension('tabulator')는 자원 사전 로딩 용도로 계속 쓸 수 있지만, 컴포넌트 사용 전에 반드시 지정할 필요는 없다.

이 밖에 서버 페이지에 테마와 로고를 적용하는 설정, 별도로 보호할 수 있는 /admin 대시보드, Narwhals가 지원하는 DataFrame 렌더링이 추가됐다. Tabulator는 507KB의 bokeh-tables 번들을 기본으로 불러오지 않는다. Markdown·HTML에 텍스트를 스트리밍할 때 토큰당 렌더링 비용이 대체로 일정해지도록 개선했으며, JSComponent와 ReactComponent의 렌더링 작업도 개선했다.

Panel 1.10.0은 Bokeh 3.10과 Python 3.12 이상이 필요하고 Python 3.15를 지원한다. Python 3.10·3.11 및 Bokeh 3.8·3.9 지원은 중단됐다. 설치는 conda install panel 또는 pip install panel로 할 수 있다. 전체 변경 사항은 Panel 1.10.0 변경 기록에서 확인할 수 있다.

부록: 원문 예시

text
pages = {     'Inputs': ('edit_note', inputs),     'Selection': ('checklist', selection),     'Sliders': ('tune', sliders),     'Feedback': ('notifications', feedback),     'Data': ('table_chart', table),     'Code': ('code', editor), } menu = pn.ui.MenuList(     items=[{'label': name, 'icon': icon} for name, (icon, _) in pages.items()],     active=(0,), sizing_mode='stretch_width', ) pn.ui.Page(     logo='images/panel_logo.png', sidebar=[menu], sidebar_width=140,     main=show_selected(menu, pages), height=440, ).servable()
text
import panel as pn pn.extension(template='page') slider = pn.ui.IntSlider(label='Stars', start=0, end=10, value=3) slider.servable(target='sidebar') pn.panel(pn.bind(lambda n: '⭐' * n, slider)).servable(target='main')
text
import panel as pn pn.extension(design='bootstrap') button = pn.widgets.Button(name='Run', button_type='primary') slider = pn.widgets.IntSlider(name='Stars', start=0, end=10, value=3) pn.template.FastListTemplate(     title='Stars',     sidebar=[button],     main=[slider.rx() * '⭐'], ).servable()
text
-pn.extension(design='bootstrap') +pn.extension() -button = pn.widgets.Button(name='Run', button_type='primary') -slider = pn.widgets.IntSlider(name='Stars', start=0, end=10, value=3) +button = pn.ui.Button(label='Run', color='primary') +slider = pn.ui.IntSlider(label='Stars', start=0, end=10, value=3) -pn.template.FastListTemplate( +pn.ui.Page(
text
import panel as pn from panel.io.asgi import build_asgi_app def stars():     slider = pn.ui.IntSlider(label='Stars', start=0, end=10, value=3)     return slider.rx() * '⭐' app = build_asgi_app({'/stars': stars})
text
import panel as pn from fastapi import FastAPI from panel.io.fastapi import add_application app = FastAPI() @app.get("/") async def read_root():     return {"Hello": "World"} @add_application('/panel', app=app, title='My Panel App') def create_panel_app():     slider = pn.ui.IntSlider(label='Slider', start=0, end=10, value=3)     return slider.rx() * '⭐'
text
add_applications(     {"/panel_app": create_panel_app},     app=app,     oauth_provider='azure',     oauth_key='...',     oauth_secret='...',     oauth_encryption_key='...',     cookie_secret='...', )