fcode-python

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Factorial Code — Python

Factorial Code — Python

Guidelines for writing Python that runs on Factorial Code. Runtime is Python 3.13. For the platform model (processes, modules, datastore) see
fcode-core-concepts
.
AspectGuideline
RuntimePython 3.13
Process entry file
main.py
Entry point
def main()
Parameters
fcode.context.parameters
Variables
os.getenv("X")
or
fcode.env.X
Import a module
fcode.import_module("module-slug")
在 Factorial Code 上运行 Python 的指南。运行时为 Python 3.13。有关平台模型(流程、模块、datastore),请参阅
fcode-core-concepts
。
方面指南
运行时Python 3.13
流程入口文件
main.py
入口点
def main()
参数
fcode.context.parameters
变量
os.getenv("X")
或
fcode.env.X
导入模块
fcode.import_module("module-slug")

Gotchas

注意事项

  • Define
    main()
    as the entry point, but never call it yourself — Factorial Code invokes it.
  • fcode.import_module()
    names must be hardcoded string literals
    , never variables:
    fcode.import_module("shopify-client")
    ✅,
    fcode.import_module(name)
    ❌.
  • Never alias
    fcode.i18n
    — call it literally (
    fcode.i18n("key")
    ✅,
    t = fcode.i18n
    ❌): an aliased call throws "i18n is disabled" at runtime. See
    fcode-i18n
    .
  • Datastore stores only strings/numbers —
    json.dumps
    objects before
    set
    ,
    json.loads
    after
    get
    .
  • Encrypt secrets you keep in the datastore — pass
    True
    as the third argument of
    set
    (
    fcode.datastore.set(key, token, True)
    );
    get
    decrypts transparently. Plain
    set
    stores the value in clear.
  • Use snake_case, not camelCase; follow PEP 8; add type hints where helpful.
  • Wrap the main flow in
    try/except
    , log the caught error with context via
    fcode-logs
    (see Logging), and raise actionable errors. Don't rely on global variables for state — pass it through parameters or return values.
  • Never hardcode or log secrets — read them from
    os.getenv
    .
  • 将
    main()
    定义
    为入口点,但切勿自行调用它—— Factorial Code 会调用它。
  • fcode.import_module()
    中的名称必须是硬编码的字符串字面量
    , 绝不能使用变量:
    fcode.import_module("shopify-client")
    ✅,
    fcode.import_module(name)
    ❌。
  • 切勿为
    fcode.i18n
    创建别名
    ——应直接调用它(
    fcode.i18n("key")
    ✅,
    t = fcode.i18n
    ❌):通过别名调用会在运行时抛出 "i18n is disabled"。请参阅
    fcode-i18n
    。
  • Datastore 仅存储字符串/数字——在
    set
    之前使用
    json.dumps
    序列化对象,在
    get
    之后使用
    json.loads
    反序列化。
  • 加密存放在 datastore 中的机密信息——将
    True
    作为
    set
    的第三个 参数传入(
    fcode.datastore.set(key, token, True)
    );
    get
    会透明地解密。 普通的
    set
    会以明文存储该值。
  • 使用 snake_case,不要使用 camelCase;遵循 PEP 8;并在有帮助的地方 添加类型提示。
  • 使用
    try/except
    包裹主流程,通过
    fcode-logs
    记录捕获的错误及其上下文 (参见“日志”),然后抛出便于定位和处理的错误。不要依赖全局变量保存状态—— 应通过参数或返回值传递状态。
  • 切勿硬编码或记录机密信息——请通过
    os.getenv
    读取。

Process template

流程模板

python
def main():
    parameters = fcode.context.parameters

    # Your code here

    return { "message": "Success!" }
python
def main():
    parameters = fcode.context.parameters

    # Your code here

    return { "message": "Success!" }

Helpers

辅助函数

python
import os
python
import os

Execution / process / schedule metadata

Execution / process / schedule metadata

execution_id = fcode.execution.id process_id = fcode.execution.process.id schedule_id = fcode.execution.schedule.id # when run from a schedule timezone = fcode.execution.timezone
execution_id = fcode.execution.id process_id = fcode.execution.process.id schedule_id = fcode.execution.schedule.id # when run from a schedule timezone = fcode.execution.timezone

Workspace (team) metadata

Workspace (team) metadata

team_slug = fcode.team.slug
team_slug = fcode.team.slug

Environment variables (secrets/config)

Environment variables (secrets/config)

api_key = os.getenv("API_KEY") # or fcode.env.API_KEY
api_key = os.getenv("API_KEY") # or fcode.env.API_KEY

Import a Factorial Code module (hardcoded name only)

Import a Factorial Code module (hardcoded name only)

client = fcode.import_module("module-name") client_v1 = fcode.import_module("module-name", "v1.0.0") # pinned version tag or alias
client = fcode.import_module("module-name") client_v1 = fcode.import_module("module-name", "v1.0.0") # pinned version tag or alias

Run another process

Run another process

fcode.processes.run("process-identifier", options)
fcode.processes.run("process-identifier", options)

Translations (workspace locales — see fcode-i18n)

Translations (workspace locales — see fcode-i18n)

greeting = fcode.i18n("greetings.hello", {"name": "Ada"}) # %{name} filled in fcode.i18n("greetings.hello", {"name": "Ada"}, {"locale": "es"}) # another locale, this lookup only (value may be dynamic) locale = fcode.i18n.locale # the execution's locale
undefined
greeting = fcode.i18n("greetings.hello", {"name": "Ada"}) # %{name} filled in fcode.i18n("greetings.hello", {"name": "Ada"}, {"locale": "es"}) # another locale, this lookup only (value may be dynamic) locale = fcode.i18n.locale # the execution's locale
undefined

Logging

日志

Log through the shared
fcode-logs
module — level-gated logging inherited by every workspace. It reads the
LOG_LEVEL
team variable (
debug | info | warn | error
, default
info
) and forwards to
print
(stdout for debug/info, stderr for warn/error), so call sites read like a plain
print
:
python
log = fcode.import_module("fcode-logs")  # import once; reuse `log` everywhere, including `except`

log.info("sync started", process_slug)  # stdout when LOG_LEVEL ≤ info
log.debug(request_payload)               # stdout only when LOG_LEVEL=debug
log.warn("token missing — skipping")      # stderr when LOG_LEVEL ≤ warn
log.error("sync failed", str(err))         # stderr — always emitted
Be verbose — the logging policy (start/end, external calls and decisions at
info
; payloads at
debug
; always log inside
except
with context before re-raising) is in
fcode-core-concepts
§General rules. Set
LOG_LEVEL=debug
in a local or dev workspace to trace a full run; production stays at
info
. Never log secrets. Each execution keeps at most 500 log lines / 500 KB — later entries are dropped (
fcode-core-concepts
› Plans & limits).
通过共享的
fcode-logs
模块记录日志——它采用级别控制的日志机制,并被每个 工作区继承。它读取团队变量
LOG_LEVEL
(
debug | info | warn | error
,默认为
info
),并转发到
print
(
debug
/
info
输出到 stdout,
warn
/
error
输出到 stderr),因此调用处 看起来就像普通的
print
:
python
log = fcode.import_module("fcode-logs")  # import once; reuse `log` everywhere, including `except`

log.info("sync started", process_slug)  # stdout when LOG_LEVEL ≤ info
log.debug(request_payload)               # stdout only when LOG_LEVEL=debug
log.warn("token missing — skipping")      # stderr when LOG_LEVEL ≤ warn
log.error("sync failed", str(err))         # stderr — always emitted
请务必详细记录——日志策略(开始/结束、外部调用和决策使用
info
; 有效载荷使用
debug
;始终在
except
中记录上下文,然后重新抛出)见
fcode-core-concepts
§General rules。在本地或开发工作区中设置
LOG_LEVEL=debug
以跟踪完整运行;生产环境保持为
info
。 切勿记录机密信息。每次执行最多保留 500 行日志 / 500 KB——后续条目 将被丢弃(
fcode-core-concepts
› Plans & limits)。

Dependencies

依赖项

External pip packages install automatically — just
import
them. When the package name differs from the import name, declare it with
@add-package
:
python
undefined
外部 pip 包会自动安装——直接
import
即可。当包名与导入名不同时, 使用
@add-package
声明:
python
undefined

@add-package requests

@add-package requests

import requests

Packages a parent workspace provides are already installed here — don't
redeclare them (see `fcode-core-concepts`).
import requests

父工作区提供的包已在此安装——不要重复声明
(参见 `fcode-core-concepts`)。

Datastore & storage

Datastore 与存储

python
import json, os
python
import json, os

Datastore (strings/numbers only)

Datastore (strings/numbers only)

fcode.datastore.set("key", "value") fcode.datastore.set("key", json.dumps({ "name": "John", "age": 30 })) value = fcode.datastore.get("key") fcode.datastore.delete("key")
fcode.datastore.set("key", "value") fcode.datastore.set("key", json.dumps({ "name": "John", "age": 30 })) value = fcode.datastore.get("key") fcode.datastore.delete("key")

Encrypted at rest with a key owned by the workspace — for tokens, credentials,

Encrypted at rest with a key owned by the workspace — for tokens, credentials,

personal data. The third argument must be a bool; get() is transparent.

personal data. The third argument must be a bool; get() is transparent.

Keys (entry names) are never encrypted.

Keys (entry names) are never encrypted.

fcode.datastore.set("oauth.token", access_token, True) token = fcode.datastore.get("oauth.token")
fcode.datastore.set("oauth.token", access_token, True) token = fcode.datastore.get("oauth.token")

Storage (files)

Storage (files)

local_path = os.path.join(os.environ.get("TMP_DATA_DIR"), "localfile.txt")
with open(local_path, "rb") as f: obj = fcode.storage.upload("path/myfile.txt", f)
objects = fcode.storage.list()
content = fcode.storage.download("path/myfile.txt") with open(local_path, "wb") as f: f.write(content)
local_path = os.path.join(os.environ.get("TMP_DATA_DIR"), "localfile.txt")
with open(local_path, "rb") as f: obj = fcode.storage.upload("path/myfile.txt", f)
objects = fcode.storage.list()
content = fcode.storage.download("path/myfile.txt") with open(local_path, "wb") as f: f.write(content)

Form file params arrive as "fcode.storage://…" references — strip the

Form file params arrive as "fcode.storage://…" references — strip the

prefix before download; see fcode-forms.

prefix before download; see fcode-forms.

Signed download URL — { "url", "expiresAt" }. A real HTTPS link in the

Signed download URL — { "url", "expiresAt" }. A real HTTPS link in the

cloud, a file:// URL locally (same shape, no special-casing).

cloud, a file:// URL locally (same shape, no special-casing).

signed = fcode.storage.create_signed_url("path/myfile.txt")
fcode.storage.delete("path/myfile.txt")

**Local disk:** write temp files under `os.environ.get("TMP_DATA_DIR")`.
signed = fcode.storage.create_signed_url("path/myfile.txt")
fcode.storage.delete("path/myfile.txt")

**本地磁盘:**将临时文件写入 `os.environ.get("TMP_DATA_DIR")` 指定的目录。

Variables, schedules & OAuth

变量、计划与 OAuth

Read/write team variables and manage process schedules at runtime — scoped to your own team, no API token needed (like datastore/storage):
python
undefined
在运行时读写团队变量并管理流程计划——这些操作的范围限定为您自己的团队, 无需 API token(与 datastore/storage 一样):
python
undefined

Team variables (config/secrets)

Team variables (config/secrets)

Default is SENSITIVE: fcode.variables.set(key, value) creates a sensitive

Default is SENSITIVE: fcode.variables.set(key, value) creates a sensitive

(masked, immutable-sensitivity) variable. Pass sensitive=False for plain

(masked, immutable-sensitivity) variable. Pass sensitive=False for plain

config values.

config values.

fcode.variables.set("API_KEY", "secret") # sensitive by default fcode.variables.set("BASE_URL", "https://api.acme.com", sensitive=False) # required for non-secret config v = fcode.variables.get("API_KEY") # TeamVariable (has resolving_team_slug) or None all_vars = fcode.variables.list() # includes variables inherited from parents fcode.variables.delete("API_KEY") # no-op on an inherited variable
fcode.variables.set("API_KEY", "secret") # sensitive by default fcode.variables.set("BASE_URL", "https://api.acme.com", sensitive=False) # required for non-secret config v = fcode.variables.get("API_KEY") # TeamVariable (has resolving_team_slug) or None all_vars = fcode.variables.list() # includes variables inherited from parents fcode.variables.delete("API_KEY") # no-op on an inherited variable

Schedules (cron or one-off date_time) for a process

Schedules (cron or one-off date_time) for a process

schedule = fcode.schedule.create( "my-process", cron="0 0 6 * * SUN", # or: date_time="2026-04-24T12:30:00.000" parameters={"foo": "bar"}, allow_concurrent_executions=False, # optional ) schedules = fcode.schedule.list(process_id=fcode.execution.process.id) current = fcode.schedule.get(schedule["id"]) fcode.schedule.update(schedule["id"], cron="0 0 7 * * SUN") fcode.schedule.pause(schedule["id"]) fcode.schedule.resume(schedule["id"]) fcode.schedule.delete(schedule["id"])
schedule = fcode.schedule.create( "my-process", cron="0 0 6 * * SUN", # or: date_time="2026-04-24T12:30:00.000" parameters={"foo": "bar"}, allow_concurrent_executions=False, # optional ) schedules = fcode.schedule.list(process_id=fcode.execution.process.id) current = fcode.schedule.get(schedule["id"]) fcode.schedule.update(schedule["id"], cron="0 0 7 * * SUN") fcode.schedule.pause(schedule["id"]) fcode.schedule.resume(schedule["id"]) fcode.schedule.delete(schedule["id"])

delete every schedule for a process (pass the process UUID)

delete every schedule for a process (pass the process UUID)

fcode.schedule.delete_for_process(fcode.execution.process.id)
fcode.schedule.delete_for_process(fcode.execution.process.id)

Start a third-party OAuth authorization. The platform holds the state, the PKCE

Start a third-party OAuth authorization. The platform holds the state, the PKCE

verifier and the one redirect_uri registered with the provider, and invokes

verifier and the one redirect_uri registered with the provider, and invokes

on_complete
when the provider redirects back -- so never build an authorization

on_complete
when the provider redirects back -- so never build an authorization

URL or a state by hand, and never expose a callback webhook.

URL or a state by hand, and never expose a callback webhook.

flow = fcode.oauth.start( authorize_url="https://login.example.com/authorize", client_id=fcode.env.PROVIDER_CLIENT_ID, scope=["openid", "offline_access"], on_complete="oauth-callback", # the process that receives the code data={"companyId": company_id}, # carried back untouched; never a secret extra_params={"nonce": nonce}, # optional, provider-specific pkce=True, # default ) flow.authorization_url # hand this to the form's oauth widget

`on_complete` is invoked with `code`, `codeVerifier`, `redirectUri`, `data` and
`state` in `fcode.context.parameters` (or `error` / `errorDescription` when the
provider refused). Replay `redirectUri` in the token exchange — providers compare
it byte for byte. See `fcode-examples` `references/oauth-connect.md`.

`fcode.variables.set/delete` only persist server-side; they are not reflected in
`fcode.env` within the same run (`fcode.env` is a snapshot taken at start).
flow = fcode.oauth.start( authorize_url="https://login.example.com/authorize", client_id=fcode.env.PROVIDER_CLIENT_ID, scope=["openid", "offline_access"], on_complete="oauth-callback", # the process that receives the code data={"companyId": company_id}, # carried back untouched; never a secret extra_params={"nonce": nonce}, # optional, provider-specific pkce=True, # default ) flow.authorization_url # hand this to the form's oauth widget

`on_complete` 会通过 `fcode.context.parameters` 接收 `code`、`codeVerifier`、
`redirectUri`、`data` 和 `state`(如果提供方拒绝请求,则接收 `error` /
`errorDescription`)。在令牌交换时原样回传 `redirectUri`——提供方会逐字节
进行比较。请参阅 `fcode-examples` `references/oauth-connect.md`。

`fcode.variables.set/delete` 仅在服务端持久化;它们不会反映到同一次运行内的
`fcode.env` 中(`fcode.env` 是启动时获取的快照)。

Inherited variables

继承的变量

list()
/
get()
include variables inherited from parent workspaces (model in
fcode-core-concepts
); an inherited one carries
resolving_team_slug
naming its owner.
set()
on an inherited key creates an override in this workspace — the only way to change the value from here — and
delete()
on one is a silent no-op, so an uninstall process never removes a parent's credential (check
resolving_team_slug
if it must report what it actually removed).
fcode.env.set_env_var
/
del_env_var
behave the same way.
list()
/
get()
包含从父工作区继承的变量(模型见
fcode-core-concepts
);继承变量会携带
resolving_team_slug
来标识其所有者。 在继承的键上执行
set()
会在当前工作区创建一个覆盖值——这是从这里 更改该值的唯一方式——而在继承变量上执行
delete()
是静默无操作, 因此卸载流程绝不会移除父工作区的凭据(如果必须报告实际删除了什么,请检查
resolving_team_slug
)。
fcode.env.set_env_var
/
del_env_var
的行为也相同。

Sending email

发送电子邮件

Send email with the built-in
fcode.send_mail
— no SMTP setup required (model in
fcode-core-concepts
):
python
info = fcode.send_mail(
    to="user@example.com",   # string or list[str]
    subject="Report ready",
    text="Plain-text body",  # provide text, html, or both
    html="<b>HTML body</b>",
)
使用内置的
fcode.send_mail
发送电子邮件——无需配置 SMTP(模型见
fcode-core-concepts
):
python
info = fcode.send_mail(
    to="user@example.com",   # string or list[str]
    subject="Report ready",
    text="Plain-text body",  # provide text, html, or both
    html="<b>HTML body</b>",
)

info => { "messageId", "accepted", "rejected" }

info => { "messageId", "accepted", "rejected" }


- The `From` address is fixed by the platform; a `from` you pass is ignored.
- Each execution can send up to 3 emails by default; once the limit is reached, further calls throw.
- Locally (`fcode run`) there is no manager, so the email is logged, not sent.
- Acceptable use — who may be a recipient, what the body may carry — is a
  platform rule that blocks a release when broken; see `fcode-core-concepts`
  §Sending email.

- `From` 地址固定由平台决定;传入的 `from` 会被忽略。
- 每次执行默认最多可发送 3 封电子邮件;达到上限后,进一步调用会抛出异常。
- 在本地(`fcode run`)没有 manager,因此电子邮件只会被记录,不会实际发送。
- 可接受的使用方式——哪些人可以作为收件人、正文可以携带哪些内容——属于平台规则;
  当违反这些规则时,会阻止发布;参见 `fcode-core-concepts`
  §Sending email。

Return values

返回值

python
undefined
python
undefined

Standard

Standard

return { "message": "Success!" }
return { "message": "Success!" }

Custom HTTP status (webhooks)

Custom HTTP status (webhooks)

return { "status": 404, "body": { "message": "Not found" }, "headers": { "Content-Type": "application/json" } }
return { "status": 404, "body": { "message": "Not found" }, "headers": { "Content-Type": "application/json" } }

Transient (not persisted in execution results)

Transient (not persisted in execution results)

return { "transient": True, "data": sensitive_data }
return { "transient": True, "data": sensitive_data }

Synchronous UI trigger button inside Factorial (see fcode-ui-triggers).

Synchronous UI trigger button inside Factorial (see fcode-ui-triggers).

Returned unchanged to the page; signal a failure with an error, not in the body.

Returned unchanged to the page; signal a failure with an error, not in the body.

return { "synced": 42 }

The return value is capped at 500 KB — larger results fail the execution; put them
in storage and return a reference (`fcode-core-concepts` › Plans & limits).
return { "synced": 42 }

返回值上限为 500 KB——更大的结果会导致执行失败;请将其放入 storage 并返回引用
(`fcode-core-concepts` › Plans & limits)。