以编程方式运行 marimo 后端:基于 ASGI 与 FastAPI 的应用集成部署指南
以编程方式运行 marimo 后端基于 ASGI 与 FastAPI 的应用集成部署指南【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo本文面向需要把 marimo 响应式笔记本嵌入到自有 Python Web 服务的开发者通过marimo.create_asgi_app()这一公开 API可以在一套 ASGI/FastAPI 应用中挂载一个或多个 marimo 应用并自由叠加认证、路由、错误处理等自定义中间件。读完本文你将掌握静态挂载with_app、动态目录with_dynamic_directory、在笔记本内访问请求数据mo.app_meta().request、查询参数校验以及底层内核模型与横向扩展约束能够独立搭建登录保护 多 notebook 仪表盘一体的生产级部署。为什么需要以编程方式运行 marimo 后端日常开发中我们通常用marimo edit/marimo run命令启动编辑器或应用。但当 marimo 只是更大应用的一部分时——例如需要与已有 FastAPI 服务共享端口、需要自定义认证逻辑、需要把十几个 notebook 聚合到一个域名下、需要为不同路由挂不同中间件——命令行启动方式就不够用了。marimo 为此提供了模块级编程接口核心入口是定义在 marimo/_server/asgi.py 中的create_asgi_app()。它返回一个ASGIAppBuilder构建器通过链式调用声明挂载点最终build()出一个标准 ASGI 应用ASGIApp可以被任何 ASGI 服务器uvicorn 等直接运行也可以被 FastAPI/Starlette 通过app.mount(/, ...)挂载为子应用。从源码看create_asgi_app()内部完成了几件关键工作marimo/_server/asgi.py使用get_default_config_manager()读取默认配置创建基于 Starlette 的base_app若未显式传入token默认使用空令牌AuthToken()——也就是说默认不做鉴权作者需要自己提供 AuthN/AuthZ强制校验pyzmq依赖已安装DependencyManager.zmq.require(...)因为每个 notebook 的运行时内核依赖它每个挂载的 notebook 都会创建独立的SessionManagerSessionMode.RUN即仅运行模式不启动 LSP 服务并复用同一份create_asgi_app级配置。create_asgi_app 参数详解create_asgi_app()的完整签名与参数语义可直接在源码 docstring 中确认marimo/_server/asgi.py参数类型默认值作用quietboolFalse是否抑制标准输出include_codeboolFalse是否把 notebook 代码包含进应用前端可查看代码tokenstr | NoneNone应用鉴权令牌不传则使用空令牌即关闭内置鉴权交由上层中间件处理skew_protectionboolFalse启用版本偏差保护中间件服务器更新后提示客户端刷新避免前后端版本不一致session_ttlint120会话存活时间秒默认 120 秒2 分钟asset_urlstr | NoneNone自定义静态资源 URL支持{version}占位符例如 CDN 地址redirect_console_to_browserboolFalse是否把 stdout/stderr 重定向到浏览器控制台显示show_tracebacksboolFalse出错时是否以模态框展示完整 traceback源码中会通过配置覆盖注入到每个 app见 marimo/_server/asgi.pyhtml_headstr | NoneNone注入每个 notebook 页面head的自定义 HTML用于全局分析脚本、自定义样式、meta 标签notebook 自带html_head_file配置时全局内容先注入、notebook 内容随后注入execute_opengraph_generatorsboolFalse解析元数据时是否执行 notebook 内定义的 OpenGraph 生成器仅对可信目录的 notebook 开启FastAPI 集成示例把 marimo 应用嵌入 FastAPI 的最小完整示例与官方文档一致可在 examples/frameworks/fastapi/main.py 看到同款生产级实现from typing import Annotated, Callable, Coroutine from fastapi.responses import HTMLResponse, RedirectResponse import marimo from fastapi import FastAPI, Form, Request, Response # 创建 marimo asgi 应用 server ( marimo.create_asgi_app() .with_app(path, root./pages/index.py) .with_app(path/dashboard, root./pages/dashboard.py) .with_app(path/sales, root./pages/sales.py) ) # 创建 FastAPI 应用 app FastAPI() app.add_middleware(auth_middleware) app.add_route(/login, my_login_route, methods[POST]) app.mount(/, server.build()) # 运行服务器 if __name__ __main__: import uvicorn uvicorn.run(app, hostlocalhost, port8000)几个关键点with_app(path, ...)挂载在根路径作为默认/首页应用build()内部会对挂载列表按路径长度降序排序确保根路径应用最后挂载见 marimo/_server/asgi.py对非空路径如/dashboard构建器会自动注册一条301重定向路由把/dashboard重定向到/dashboard/避免尾斜杠缺失导致 404见 marimo/_server/asgi.pyFastAPI 自有路由如/login与 marimo 挂载在同一应用上二者互不冲突。静态资源与认证豁免注意在这种模式下marimo 会把静态资源挂载在notebook 名称之下例如上例中就是http://hostname/dashboard|sales/assets/assetname.css|js|...。如果你使用了自定义授权中间件务必对这些静态资源路径跳过认证检查——它们的数量非常多如果每个资源都触发一次鉴权往返页面加载会被严重拖慢。可以在中间件里通过request.url.path判断前缀/assets/后直接放行仓库的冒烟测试 marimo/_smoke_tests/custom_server/my_server.py 中即是先放行/login、再校验 cookie 令牌的模式。完整示例的进一步参考仓库中的 examples/frameworks/fastapi/main.py 是一个更完整的参考实现它演示了遍历目录批量把每个.pynotebook 挂载成独立路由for filename in sorted(os.listdir(ui_dir))基于 Session 的登录/登出流程SessionMiddlewareJinja2Templates首页列出所有已挂载应用并支持跳转.env环境变量加载与日志配置。对应 READMEexamples/frameworks/fastapi/README.md说明其运行方式为uv run --no-project main.py。静态目录遍历批量挂载如果目录中的 notebook 基本不变用with_app加循环遍历是更推荐的方式——每个 notebook 在启动时就静态注册路由固定、行为可预期from pathlib import Path server marimo.create_asgi_app() app_names: list[str] [] notebooks_dir Path(__file__).parent / notebooks for filename in sorted(notebooks_dir.iterdir()): if filename.suffix .py: app_name filename.stem server server.with_app(pathf/{app_name}, rootfilename) app_names.append(app_name)with_app的签名源码 marimo/_server/asgi.py为with_app(*, path: str, root: str, middleware: list[MiddlewareFactory] | None None)其中path应用挂载的 URL 路径rootnotebook 文件路径middleware可选仅应用于该子应用的中间件工厂列表。注意构建器会为每个唯一root缓存并复用已创建的 ASGI 子应用self._app_cache相同文件不会重复创建会话见 marimo/_server/asgi.py。动态目录with_dynamic_directory当目录内容频繁变化例如仪表盘 notebook 会新增、删除且你不想为每次变更重启服务器时使用with_dynamic_directoryserver ( marimo.create_asgi_app() .with_dynamic_directory(path/dashboard, directory./notebooks) )其完整签名marimo/_server/asgi.py为with_dynamic_directory( *, path: str, directory: str, validate_callback: ValidateCallback | None None, middleware: list[MiddlewareFactory] | None None, ) - ASGIAppBuilder其中validate_callback的类型别名是Callable[[str, Scope], Awaitable[bool] | bool]marimo/_server/asgi.py它接收应用路径与请求 scope返回布尔值表示该应用是否允许访问非常适合插入认证/授权逻辑回调中也可以主动抛出带status_code与headers的异常来返回自定义错误信息。从DynamicDirectoryMiddleware的实现marimo/_server/asgi.py可以梳理出该模式的底层行为路由解析请求路径去掉base_path前缀后先按相对路径.py直接匹配再按前缀逐级尝试嵌套匹配_find_matching_filemarimo/_server/asgi.py支持多级子目录结构安全防护显式拒绝..路径穿越段包括把\归一化为/后检查防止 Windows 风格分隔符绕过并用path.resolve().relative_to(directory.resolve())实时校验目标文件确实位于目录内marimo/_server/asgi.py应用缓存与惰性加载每个匹配到的 notebook 首次请求时才创建对应 ASGI 子应用并缓存在_app_cache目录内容变化无需重启这也是动态的来源marimo/_server/asgi.py尾斜杠重定向HTTP 请求若缺少尾斜杠且无剩余路径会返回307重定向补齐/marimo/_server/asgi.py子路径挂载兼容当该 ASGI 应用被父框架挂载到子路径如app.mount(/server2, ...)时会正确处理root_path与路径前缀的剥离逻辑marimo/_server/asgi.py。仓库冒烟测试 marimo/_smoke_tests/custom_server/my_server.py 同时验证了根挂载与/server2子路径挂载两种场景并注明是对某个 GitHub issue 的回归测试。一个可供参考的冒烟测试组合展示了with_appwith_dynamic_directory 根应用混用的完整形态marimo/_smoke_tests/custom_server/my_server.pyserver1 ( marimo.create_asgi_app() .with_app(path/dataframes, rootstr(dirname / dataframe.py)) .with_dynamic_directory(path/charts, directorystr(dirname / altair_examples)) .with_dynamic_directory(path/smoke_tests, directorystr(dirname)) .with_app(path, rootstr(dirname / buttons.py)) )在笔记本内访问请求数据mo.app_meta().request以编程方式挂载的 notebook 中可以通过mo.app_meta().request拿到当前 HTTP 请求数据这对实现认证、展示用户信息非常有用import marimo as mo # 在 notebook 中访问请求数据 request mo.app_meta().request if request and request.user and request.user[is_authenticated]: content fWelcome {request.user[username]}! else: content Please log in mo.md(content)AppMeta.request属性定义在 marimo/_runtime/app_meta.py它从运行时上下文中读取请求未初始化时返回None。其返回类型是HTTPRequest定义于 marimo/_runtime/commands.py这是一个可 pickle 的 dataclass只包含安全的请求子集具体字段字段含义request.headers请求头已剔除marimo/x-marimo前缀的内部头request.cookies请求 Cookierequest.query_params查询参数映射为dict[str, list[str]]request.path_params路由路径参数request.user由认证中间件写入的用户数据request.urlURL 信息含path、port、scheme、netloc、query、hostnamerequest.base_url序列化的基础 URLrequest.meta自定义中间件写入的元数据特别值得注意的设计HTTPRequest刻意不包含 session 与 auth 字段源码注释明确说明they may contain information that the app author does not want to expose避免应用作者无意间暴露敏感数据。认证中间件实现要让request.user有值需要实现一个纯 ASGI 中间件重要警告请使用纯 ASGI 中间件而不是 Starlette 的BaseHTTPMiddleware这样才能保证scope[user]和scope[meta]对HTTP 和 WebSocket连接都生效。marimo 使用 WebSocket 做实时通信而BaseHTTPMiddleware只处理 HTTP 请求。class AuthMiddleware: def __init__(self, app): self.app app async def __call__(self, scope, receive, send): if scope[type] in (http, websocket): # 向请求 scope 中写入用户数据 # 该数据可通过 mo.app_meta().request.user 访问 scope[user] { is_authenticated: True, username: example_user, # 可添加任意其他用户数据 } # 可选向请求添加元数据 scope[meta] { some_key: some_value, } await self.app(scope, receive, send) # 将中间件添加到 FastAPI 应用 app.add_middleware(AuthMiddleware)其数据链路在源码中清晰可循中间件向 ASGIscope写入user/meta后HTTPRequest.from_request通过_user_to_dict(request.get(user))与_meta_to_dict(request.get(meta))将其序列化进HTTPRequestmarimo/_runtime/commands.pynotebook 侧再经mo.app_meta().request读取marimo/_runtime/app_meta.py。仓库的 FastAPI 示例 examples/frameworks/fastapi/main.py 展示了更贴近生产的会话式认证登录后把用户名写入 session中间件对所有非/login请求校验 session未登录则302重定向到登录页。文档化并校验查询参数挂载的应用接收查询参数时可以借助 Pydantic 模型声明、校验并文档化这些参数。假设 marimo 应用notebooks/items.py被挂载到/items那么可以在 FastAPI 中声明同路由的端点查询参数先经过 Pydantic 模型校验再重定向到 marimo 端点校验失败时 FastAPI 会自动返回 422 及清晰错误信息从而起到文档化 校验的双重作用# src/main.py from fastapi import FastAPI, Request, Query from fastapi.responses import RedirectResponse from marimo import create_asgi_app from pathlib import Path from pydantic import BaseModel, Field from typing import Annotated, Literal from urllib.parse import urlencode app FastAPI() class FilterParams(BaseModel): limit: int Field(100, gt0, le100) offset: int Field(0, ge0) order_by: Literal[created_at, updated_at] created_at tags: list[str] [] app.get(/items) async def marimo_items( request: Request, filter_query: Annotated[FilterParams, Query()] ): query_params urlencode(filter_query.model_dump(), doseqTrue) return RedirectResponse(urlf/items/?{query_params}) server create_asgi_app(include_codeTrue, quietFalse) notebooks_dir Path(__file__).parent.parent / notebooks for filename in notebooks_dir.iterdir(): if filename.suffix .py: app_name filename.stem server server.with_app(pathf/{app_name}, rootfilename) app.mount(/, server.build())要点说明FilterParams对每个字段声明了默认值与约束limit在 1100、offset非负、order_by限定枚举、tags支持列表Pydantic 校验不通过时 FastAPI 会直接返回 422校验通过后使用urlencode(..., doseqTrue)把model_dump()的结果重新编码为查询串doseqTrue保证tags这类列表参数正确展开然后RedirectResponse指向 marimo 端点/items/该模式在文档 docs/api/query_params.md 中亦有呼应notebook 内部可用mo.query_params读取这些参数并用 Pydantic 模型如 RGB 通道字段设置 UI 初始状态。底层机制与横向扩展约束Under the Hood在这种模式下marimo 会为每个新会话/每个挂载应用在独立子线程中同一进程内启动一个新的计算内核。由此带来性能和可靠性方面的影响必须启用粘性会话sticky sessions如果运行多个相同服务器实例做负载均衡负载均衡器必须确保同一客户端每次请求都命中同一实例——因为用户的内核只存在于其首次连接的那台服务器上同一节点上多进程不可靠在同一节点运行同一 FastAPI 进程的多个实例Python Web 服务的常见做法无法可靠工作因为实际运行内核的只会是其中一个实例扩展建议上述方案的横向扩展存在天然上限官方建议优先纵向扩展——先提升容器 CPU/内存规格再考虑增加容器实例数。补充一个源码层面的背景create_asgi_app()创建每个子应用时读取实验性配置experimental.isolate_apps默认False见 marimo/_server/asgi.py用于控制是否对应用做进程级隔离。仓库中的进程隔离冒烟测试 marimo/_smoke_tests/process_isolation/serve.py 说明了这一特性解决的问题当两个 notebook 各自 import 同名但内容不同的模块时进程隔离能避免sys.modules相互污染该脚本用create_asgi_app()挂载两个应用并验证各自输出 PASS/FAIL。这也意味着在评估每个应用一个内核的部署成本时需要考虑内核/进程数量随 notebook 数量线性增长。小结marimo.create_asgi_app()提供了一条干净的编程式嵌入路径with_app适合静态路由、with_dynamic_directory适合频繁变动的目录、mo.app_meta().request打通了 notebook 与 Web 请求之间的数据通道而纯 ASGI 中间件 Pydantic 查询参数校验则让认证与参数治理完全可控。部署时只需记住两条铁律静态资源路径要在鉴权中间件中豁免多实例部署必须开启粘性会话并优先纵向扩容。完整的可运行参考与冒烟测试分别位于 examples/frameworks/fastapi/main.py 与 marimo/_smoke_tests/custom_server/my_server.py。【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →