跳转至

脚本开发 / 基本概念

DataFlux Func 中,存在一些 DataFlux Func 中特有的概念,本文档将对此进行说明。

1. 脚本集、脚本和函数

脚本集、脚本和函数可在「开发 / 脚本库」中创建,是 DataFlux Func 的核心概念,ID 在用户创建 / 编写代码时直接指定。

  • 「脚本集」为数个脚本的集合,ID 在创建时由用户直接指定,且只能包含脚本。
  • 「脚本」即 Python 脚本本身,必然属于某一个脚本集,ID 在创建时由用户直接指定。
  • 「函数」在 DataFlux Func 中特指被 @DFF.API(...) 装饰器装饰的最顶层函数,可以被函数 API、定时任务等作为调用的入口函数。

脚本集不是文件夹

脚本集与文件夹类似,但这个「文件夹」与一般 Python 编码中的文件夹无关

在 DataFlux Func 中进行编码时,会大量涉及到脚本集、脚本和函数的 ID,并且这些 ID 之间存在紧密的联系。

脚本集、脚本和函数 ID 之间关系

按照脚本集、脚本和函数层级关系,下层概念的 ID 一定包含了上层概念的 ID。

假设存在一个 ID 为 demo 的脚本集,那么属于此脚本集的所有脚本必然以 demo__(双下划线)开头。

再假设此脚本集下有 ID 为 demo__test的脚本,它包含一个函数 def hello(...),那么这个函数的 ID 即为 demo__test.hello

ID 示例表如下:

概念 ID 示例
脚本集 demo
脚本 demo__test
函数 demo__test.hello

编码中相互引用

在 DataFlux Func 的脚本中,允许引用另一脚本来实现代码复用。

假设存在脚本 demo__script_a,其包含一个函数 func_a()。那么,在脚本 demo__script_b 中引用此函数时,可以使用如下方法:

demo__script_a
1
2
def func_a():
    pass
demo__script_b
1
2
3
4
import demo__script_b

def test():
    return demo__script_b.func_a()

Python 的as语句也同样可以使用:

demo__script_b
1
2
3
4
import demo__script_b as b

def test():
    return b.func_a()

也可以使用 from ... import 语句只导入所需函数:

demo__script_b
1
2
3
4
from demo__script_b import func_a

def test():
    return func_a()

对于属于同一个脚本集的脚本之间引用,可以忽略脚本集 ID,以 __(双下划线)开头的缩略形式表示:

demo__script_b
1
2
3
4
from __script_b import func_a

def test():
    return func_a()

尽可能使用缩略形式

在脚本集内部相互引用应当尽量使用缩略形式(即忽略脚本集 ID 并以 __ 开头的形式)。

这样,在将整个脚本集克隆,脚本集 ID 发生改变后,克隆出来的新脚本集内的代码依然可以正确引用本脚本集内的脚本。

2. 连接器

连接器可在「开发 / 连接器」中创建,是由 DataFlux Func 提供的连接外部系统的工具,ID 在创建时由用户直接指定。

实际上,在 DataFlux Func 编写 Python 代码与原版 Python 并无太大区别。开发者完全可以忽略连接器,自行在代码中连接外部系统。

但对于一些存在连接池概念的外部系统来说,连接器内置了连接池,可在函数反复运行的过程中保持链接,避免反复创建 / 关闭与外部系统的连接。

假设用户已经配置好了一个 ID 为 mysql 的连接器,那么获取这个连接器的操作对象代码如下:

Python
1
mysql = DFF.CONN('mysql')

具体不同的连接器具有不同的操作方法和参数,详情请参考 脚本开发 / 连接器对象 DFF.CONN

3. 环境变量

环境变量可在「开发 / 环境变量」中创建,是由 DataFlux Func 提供的简单 Key-Value 配置读取工具,ID 在创建时由用户直接指定。

环境变量特别适合用于同一套代码运行在不同环境下的场景。

如脚本需要访问的系统区分了测试 / 生产环境,那么可以通过设置环境变量,实现在不改变代码的情况下,切换测试 / 生产环境。

假设用户已经配置好了一个 ID 为 api_endpoint 的环境变量,那么获取这个环境变量值的代码如下:

Python
1
api_endpoint = DFF.ENV('api_endpoint')

4. 函数 API

函数 API 可在「管理 / 函数 API」中创建,是外部调用 DataFlux Func 中函数的一种常见方式,调用过程可选同步或异步,同步执行时,函数执行完成后可以将结果直接返回给调用方。

为函数创建函数 API 后,支持多种不同的调用方法。

函数 API 支持 GETPOST 两种方式。两种不同方式的参数传递同时支持「简化形式」、「标准形式」。

此外,POST 方式的「简化」形式还支持文件上传,以下是各种调用方式的功能支持列表:

调用方式 传递 kwargs 参数 kwargs 参数类型 传递 options 文件上传 提交任意格式的 Body
GET 简化形式 支持 仅限字符串 不支持 不支持 不支持
GET 标准形式 支持 JSON 中的数据类型 支持 不支持 不支持
POST 简化形式 支持 仅限字符串 不支持 支持 支持
POST 标准形式 支持 JSON 中的数据类型 支持 不支持 不支持

传递方式不同会导致参数类型存在限制

对于 kwargs 中参数只能传递字符串的调用方式,需要在函数中对参数进行类型转换。在函数 API 列表,可以点击「API 调用示例」来查看具体调用方式

假设存在如下函数:

Python
1
2
3
@DFF.API('我的函数')
def my_func(x, y):
    pass

假设为此函数创建的「函数 API」ID 为 func-api-xxxxx,传递的参数为 x=100(整数), y="hello"(字符串)。

那么,各种不同的调用方式如下:

GET 简化形式传参

如果函数的参数比较简单,可以使用 GET 简化形式传递参数,接口将更加直观。

由于 URL 中传递参数时,无法区分字符串的 "100" 和整数的 100, 所以函数在被调用时,接收到的参数都是字符串。 函数需要自行对参数进行类型转换。

Text Only
1
GET /api/v1/al/func-api-xxxxx/s?x=100&y=hello

为了便于阅读,示例为 URLEncode 之前的内容,实际 URL 参数需要进行 URLEncode

GET 标准形式传参

在某些情况下,如果无法发送 POST 请求,也可以使用 GET 方式调用接口。

GET 标准形式传参时,将整个 kwargs 进行 JSON 序列化后,作为 URL 参数传递即可。 由于参数实际还是以 JSON 格式发送,因此参数的原始类型都会保留。 函数无需再对参数进行类型转换。

如本例中,函数接收到的 x 参数即为整数,无需类型转换。

Text Only
1
GET /api/v1/al/func-api-xxxxx?kwargs={"x":100,"y":"hello"}

为了便于阅读,示例为 URLEncode 之前的内容,实际 URL 参数需要进行 URLEncode

POST 简化形式传参

在某些情况下,如果无法发送请求体为 JSON 的 HTTP 请求, 那么也可以类似 Form 表单的形式传递参数,各字段名即为参数名。

由于 Form 表单提交数据时,无法区分字符串的 "100" 和整数的 100,所以函数在被调用时,接收到的参数都是字符串,函数需要自行对参数进行类型转换。

Text Only
1
2
3
4
POST /api/v1/al/func-api-xxxxx/s
Content-Type: x-www-form-urlencoded

x=100&y=hello

此外,POST 简化形式传参还额外支持文件上传(参数/字段名必须为 files), 需要使用 form-data/multipart 方式进行处理。

页面 HTML 代码示例如下:

HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
    <body>
        <h1>文件上传</h1>
        <input id="file" type="file" name="files" required />
        <input id="submit" type="submit" value="上传"/>
    </body>
    <script>
        // 函数 API 地址(如本页面与 DataFlux Func 不在同一个域名下,需要写全 http://domain:prot/api/v1/al/func-api-xxxxx/s
        // 注意:上传文件必须使用简化形式函数 API
        var API_URL = '/api/v1/al/func-api-xxxxx/s';

        document.querySelector('#submit').addEventListener('click', function(event) {
            // 点击上传按钮后,生成 FormData 对象后作为请求体发送请求
            var data = new FormData();
            data.append('x', '100');
            data.append('y', 'hello');
            data.append('files', document.querySelector('#file').files[0]);

            var xhr = new XMLHttpRequest();
            xhr.open('POST', API_URL);
            xhr.send(data);
        });
    </script>
</html>

POST 标准形式传参

POST 标准形式传参是最常见的调用方式。 由于参数以 JSON 格式通过请求体发送,因此参数的原始类型都会保留。 函数无需再对参数进行类型转换。

如本例中,函数接收到的 x 参数即为整数,无需类型转换。

Text Only
1
2
3
4
5
6
7
8
9
POST /api/v1/al/func-api-xxxxx
Content-Type: application/json

{
    "kwargs": {
        "x": 100,
        "y": "hello"
    }
}

5. 定时任务

定时任务可在「管理 / 定时任务」中创建,用于让 DataFlux Func 定期自动调用函数。

为函数创建定时任务后,函数则会所指定的 Crontab 表达式定时执行,不需要外部进行调用。

正因为如此,所执行的函数的所有参数必须都已满足,即:

  1. 函数不需要输入参数
  2. 函数需要输入参数,但都是可选参数
  3. 函数需要必选参数,并在定时任务中为其配置具体的值

区分函数运行时所属执行功能

如果函数同时配置了「定时任务」和其他执行功能,并且希望在不同的执行功能中进行区分处理,可以判断内置变量 _DFF_CRONTAB 区分:

Python
1
2
3
4
5
6
7
8
9
@DFF.API('我的函数')
def my_func(x, y):
    result = x + y

    if _DFF_CRON_EXPR:
        # 只在定时任务时输出日志
        print(f'x + y = {result}')

    return