ToolJet Run Python Code 数据源完全指南:在组件、查询与转换中编写 Python 逻辑
本文以 ToolJet 仓库中的 run-py.md 文档为主线,系统讲解 Run Python Code(runpy) 这一内置数据源的创建、能力边界与最佳实践:如何用 Python 触发组件动作、运行其他查询并读取其结果、读写变量、在数据转换(Transformations)中复用 Python 逻辑,以及把查询结果绑定回组件渲染。读完本文,你将能直接在 ToolJet 的 App Builder 中编写可复用的 Python 查询,实现比纯低代码表达式更灵活的数据处理与交互编排。
Run Python Code 是 ToolJet 内置的自定义代码类数据源,它让你不依赖 JavaScript 就能在应用内编写并执行 Python,用来与页面上的组件、已建立的查询交互,从而自定义动作逻辑与数据处理方式。在源码层面,它是与 restapi、runjs、tooljetdb、workflows 并列的一种静态数据源类型 runpy(参见 DefaultDataSourceKinds 常量),其 Python 代码实际是在浏览器端通过 Pyodide(WebAssembly 版 CPython)执行,而非在服务器端运行。
一、创建一条 Run Python Code 查询
- 在 App Builder 中打开 Query Manager(查询管理器);
- 点击 Add(添加),在数据源列表中选择 Run Python Code;
- 代码编辑区会自动打开一个 Python 语言模式的多行编辑器,输入代码后 保存并运行(Save and Run)。
从实现上看,这个编辑器由 Runpy.jsx 渲染:它基于 CodeHinter 组件,设置了 lang="python"、默认高度 400px,输入内容通过 changeOption(this, 'code', value) 写回查询配置中的 options.code 字段。因此每条 Run Python 查询本质上就是一个 code 配置项 + runpy 数据源类型的组合。
执行时,前端会通过 loadPyodide 从 PYODIDE_BASE_URL 加载 Pyodide 运行时:
export const loadPyodide = async () => {
const pyodide = await window.loadPyodide({ indexURL: process.env.PYODIDE_BASE_URL });
return pyodide;
};
理解“Python 跑在浏览器里”这一点,对后面判断哪些库可用、哪些网络能力受限非常关键。
二、用 Python 触发组件专属动作
Run Python Code 最典型的使用场景之一,是从 Python 代码里调用组件的专属动作(Component Specific Actions),把计算结果写回组件状态。
官方文档给出了一个完整示例:先用 Text 组件在画布上放置一个文本框(其组件名默认为 text1),再新建一条 Run Python Code 查询,粘贴如下代码并运行:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def myfunc(self):
return "Hello my name is " + self.name
p1 = Person(tj_globals.currentUser.firstName, 36)
components.text1.setText(p1.myfunc())
代码含义拆解:
- 定义了一个普通 Python 类
Person,包含构造函数与返回问候语的myfunc方法; tj_globals.currentUser.firstName直接读取 ToolJet 暴露的全局变量,把当前登录用户的名字注入 Python 对象——这说明 Python 与 ToolJet 运行时共享了同一套上下文数据;- 最后一行的
components.text1.setText(...)是一个 组件专属动作,等价于在 UI 上调用 Text 组件的 setText 方法,将其value更新为myfunc()的返回值。
运行后,画布上的 Text 组件便会显示类似 Hello my name is <你的名字> 的内容。把复杂字符串拼接、格式化甚至算法逻辑交给 Python,再通过组件动作写回界面,正是该数据源的核心价值。
三、在 Python 中触发其他查询
Run Python Code 可以直接驱动应用里已有的查询。文档给出两种等价写法:
actions.runQuery('getSalesData')
# 将 getSalesData 替换为你的查询名称
或:
queries.getSalesData.run()
# 将 getSalesData 替换为你的查询名称
两者都会触发名为 getSalesData 的查询开始执行。区别在于语义:actions.runQuery 走的是与事件动作(Event Handler)相同的“动作”通道,适合在 Python 逻辑的任意位置按名称触发;queries.<name>.run() 则通过查询对象 API 触发,触发后还可以配合下一小节的取值方法拿到结果。
四、获取查询返回的数据
如果你需要立即读取某查询的返回结果继续处理,可以先 await 运行它,再通过查询对象的取值方法获取数据。运行 Python 的上下文支持异步(Pyodide 基于事件循环),所以可以使用 await。
触发查询并获取其数据(Data)
await queries.getSalesData.run()
# 将 getSalesData 替换为你的查询名称
value = queries.getSalesData.getData()
# 将 getSalesData 替换为你的查询名称
value
getData() 返回查询处理后的数据(例如已应用默认映射、便于展示的数据结构)。注意最后一行直接把 value 作为代码块末尾表达式——Run Python Code 会像 REPL 一样把最后一个表达式的值作为查询输出,value 会同时显示在查询结果面板中,供后续绑定或调试。
触发查询并获取其原始数据(Raw Data)
await queries.getCustomerData.run()
# 将 getCustomerData 替换为你的查询名称
value = queries.getCustomerData.getRawData()
# 将 getCustomerData 替换为你的查询名称
value
getRawData() 返回 API/数据源返回的未经处理的原始响应。当 ToolJet 对数据做过归一化而你想拿最原始的 JSON 做自定义解析时,优先使用它。
触发查询并读取其加载状态(Loading State)
await queries.getTodos.run()
# 将 getTodos 替换为你的查询名称
value = queries.getTodos.getLoadingState()
# 将 getTodos 替换为你的查询名称
value
getLoadingState() 返回布尔值,标识查询当前是否仍在执行中。你可以在 Python 中据此做条件分支,例如:等待数据就绪后继续、否则提前返回。
需要提醒的是,await queries.<name>.run() 会阻塞 Python 执行直到查询完成,因此它天然适合“先取数、再计算、最后写回组件”的串行流程,避免竞态问题。
五、在 Python 中读写变量与页面变量
Run Python Code 还暴露了变量操作 API,可以读写应用级变量(Variables) 与页面变量(Page Variables),让 Python 与页面其余部分共享状态。
设置变量
actions.setVariable('color', 'blue')
# 将 color 替换为你想使用的变量名
设置后立即读取该变量
actions.setVariable('mode', 'dark')
# 将 mode 替换为你想使用的变量名
actions.getVariable('mode')
# 将 mode 替换为你想使用的变量名
这里存在一个细微差别:getVariable 读取的是当前快照,因此必须先 setVariable 再 getVariable 才能立即拿到新值——这正是官方示例采用“连续两行”写法的原因。
设置页面级变量
actions.setPageVariable('version', 1)
# 将 version 替换为你想使用的变量名
设置页面级变量后立即读取
actions.setPageVariable('number', 1)
# 将 number 替换为你想使用的变量名
actions.getPageVariable('number')
# 将 number 替换为你想使用的变量名
页面变量与普通变量的差异在于作用域:页面变量只在当前页面内可见,适合存放随页面生命周期的状态;普通变量作用于整个应用。关于变量体系的完整说明可参考 variables.md 概念文档。
六、使用 Python 编写数据转换(Transformations)
Run Python Code 最常见的生产用途是作为查询的 Transformations(数据转换):在 Query Editor 中启用 Transformations 开关后,Python 代码会把查询返回的数据(固定以变量名 data 暴露)加工后返回给下游组件。
下面用一个 REST API 示例走一遍完整流程:
- 新建一条 REST API 查询,Method 保持默认的 GET;
- 在 URL 属性中填入测试接口地址:
https://dummyjson.com/products
- 点击 Run 按钮,在预览区查看返回的数据结构,大致如下:
products_data = {
"products": [
{"title": "iPhone 9", ...},
{"title": "iPhone X", ...},
# Additional products...
]
}
随后,基于这套数据,Enable Transformations 即可写出下面三类典型场景代码。
场景一:从响应中过滤出标题列表
遍历 products 列表并抽取每个商品的 title:
return [product["title"] for product in data["products"]]
场景二:按分类过滤商品并输出标题
只保留分类为 smartphones 的商品标题:
return [product["title"] for product in data["products"] if product["category"] == "smartphones"]
场景三:计算某分类的平均价格
计算 laptops 分类下的平均价格,并处理“该分类为空”的边缘情况(返回 0):
return sum(product["price"] for product in data["products"] if product["category"] == "laptops") / len([product for product in data["products"] if product["category"] == "laptops"]) if len([product for product in data["products"] if product["category"] == "laptops"]) > 0 else 0
三个例子由浅入深地展示了同一个要点:Transformations 中的 Python 以 data 作为输入,以 return 的表达式作为输出,写法与普通的纯函数完全一致。因此无论是简单的字段抽取、条件过滤,还是聚合计算,Python 的标准语法都能直接胜任。
七、在组件中引用 Python 查询的返回数据
Run Python Code 查询与其他查询一样,其结果会挂载到全局查询对象上,可用双花括号表达式 {{ }} 绑定到任意组件属性。
例如你有一条名为 updatedProductInfo 的 Run Python Code 查询,希望在 Table 组件中渲染它返回的数据,只需在 Table 的 Data 属性中填入:
{{queries.updatedProductInfo.data}}
运行应用后,Table 就会以 Python 查询的返回结果(列表或对象数组)作为数据源自动渲染。这也印证了一个结论:Run Python Code 在 ToolJet 中并非“一次性脚本”,而是一条可复用的数据查询,其结果同样遵循 queries.<name>.data 的引用约定,可被表格、图表、文本等组件消费。
八、限制与避坑指南
Run Python Code 并非完整的服务器端 Python 环境,以下几点务必在使用前了解:
-
仅支持 Python 标准库:当前 runpy 运行时不提供第三方包(如
requests、pandas等),可用的只是 Python 标准库中的模块。官方文档在 run-py.md 中明确提示了这一点。 -
网络能力受限:由于 Python 基于浏览器端的 Pyodide 运行,使用标准库
urllib/urlopen打开外部 URL 时可能失败。例如尝试访问https://api.baserow.io这类公网地址会报错,这与 Pyodide 的网络约束有关。完整的已知问题与排查请查阅 runpy-limits.md 故障排查文档。 -
用 JS 互操作绕开网络限制:Pyodide 支持 Python ↔ JavaScript 互操作,可借助
from js import fetch发起 HTTP 请求。以下是向外部 API 推送 JSON 数据的官方推荐写法:
from js import fetch
import json
async def push_data(url, data):
response = await fetch(
url,
method='POST',
headers=[
["Authorization", "Token <my_token>"],
["Content-Type", "application/json"]
],
body=data
)
reply = await response.json()
return reply
url = "https://api.baserow.io/api/database/rows/table/.../?user_field_names=true"
reply = await push_data(url, json.dumps(<my_data>))
reply
上面通过 fetch 发起 POST 请求,在 Authorization 头中携带令牌,以 JSON 作为请求体。也就是说:当标准库网络能力不够时,通过 js 桥接层调用浏览器的 fetch 即可。需要注意的是,发送前请确保自己拥有对应的鉴权令牌与合法的目标数据。
- 代码运行在浏览器本地:执行不占用服务器资源,但也意味着逻辑无法访问服务器端文件、环境变量或私有网络,所有“数据来源”都应是页面上下文中能被浏览器访问到的内容。
九、小结:何时选择 Run Python Code
与 JavaScript 版的 Run JavaScript(runjs) 数据源互为补充,Run Python Code 适合以下场景:
- 团队更熟悉 Python,希望用 Python 语法完成组件交互与查询编排;
- 需要把多行、可读性强的算法逻辑(字符串处理、循环、类封装)放进查询而不是一行表达式;
- 在 Transformations 中对 API 响应做复杂的过滤、聚合与转换;
- 希望先
await一个查询完成、拿到getData()/getRawData()/getLoadingState()后再继续的时序敏感逻辑。
它会将结果挂载到 queries.<名称>.data 供组件引用,可无缝融入现有的事件流与数据绑定体系。参考源码:编辑器 UI 位于 Runpy.jsx,Pyodide 加载逻辑位于 _helpers/utils.js,runpy 静态数据源类型定义可参见 server 端常量 与 数据类型声明。遇到 urlopen 等网络相关报错时,直接查阅 RunPy 限制说明 即可对症解决。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
