flowbase-cli 0.1.12__tar.gz → 0.1.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/PKG-INFO +1 -1
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/pyproject.toml +1 -1
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli/__init__.py +1 -1
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli/endpoints.py +66 -9
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli/guide.py +2 -2
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli.egg-info/PKG-INFO +1 -1
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/tests/test_autonomy.py +16 -1
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/tests/test_cli.py +1 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/README.md +0 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/setup.cfg +0 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli/main.py +0 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli.egg-info/SOURCES.txt +0 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli.egg-info/dependency_links.txt +0 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli.egg-info/entry_points.txt +0 -0
- {flowbase_cli-0.1.12 → flowbase_cli-0.1.13}/src/flowbase_cli.egg-info/top_level.txt +0 -0
|
@@ -324,19 +324,19 @@ ENDPOINTS = (
|
|
|
324
324
|
request_body='{"baseRevision":4,"enabled":true}'),
|
|
325
325
|
Endpoint("approvals.delete", "flowbase_approvals_delete", "POST", "/objects/{object_id}/approvals/{approval_id}/delete", "metadata:write", "Logically delete an approval flow.", "flowbase approvals delete obj_xxx apr_xxx --base-revision 4 --yes", True,
|
|
326
326
|
request_body='{"baseRevision":4}'),
|
|
327
|
-
Endpoint("print-templates.list", "flowbase_print_templates_list", "GET", "/objects/{object_id}/print-templates", "metadata:read", "List object print templates.", "flowbase print-templates list obj_xxx"),
|
|
328
|
-
Endpoint("print-templates.get", "flowbase_print_templates_get", "GET", "/objects/{object_id}/print-templates/{template_id}", "metadata:read", "Get a print template with HTML and data code.", "flowbase print-templates get obj_xxx ptp_xxx"),
|
|
329
|
-
Endpoint("print-templates.create", "flowbase_print_templates_create", "POST", "/objects/{object_id}/print-templates", "metadata:write", "Create a print template draft.", "flowbase print-templates create obj_xxx --json-file template.json",
|
|
327
|
+
Endpoint("print-templates.list", "flowbase_print_templates_list", "GET", "/objects/{object_id}/print-templates", "metadata:read", "List object print templates. draftRevision is on the envelope.", "flowbase print-templates list obj_xxx"),
|
|
328
|
+
Endpoint("print-templates.get", "flowbase_print_templates_get", "GET", "/objects/{object_id}/print-templates/{template_id}", "metadata:read", "Get a print template with HTML and data code. Returns source, not rendered HTML.", "flowbase print-templates get obj_xxx ptp_xxx"),
|
|
329
|
+
Endpoint("print-templates.create", "flowbase_print_templates_create", "POST", "/objects/{object_id}/print-templates", "metadata:write", "Create a print template draft. Read docs catalogs print-templates first.", "flowbase print-templates create obj_xxx --json-file template.json",
|
|
330
330
|
request_body=(
|
|
331
331
|
'{"baseRevision":3,"name":"Sales order","key":"sales_order","description":"",'
|
|
332
332
|
'"paper":{"size":"A4","width":null,"height":null,"orientation":"portrait",'
|
|
333
333
|
'"margins":{"top":15,"right":15,"bottom":15,"left":15}},'
|
|
334
334
|
'"header":{"left":"","center":"","right":""},'
|
|
335
|
-
'"footer":{"left":"","center":"
|
|
336
|
-
'"html":"<h1>{{ record.summary }}</h1>\\n<p>{{ data.total | money(\\"CNY\\") }}</p>",'
|
|
337
|
-
'"code":"import flowbase\\n\\ndef run():\\n return {\\"total\\":
|
|
335
|
+
'"footer":{"left":"","center":"第 {page} 页 / 共 {pages} 页","right":""},'
|
|
336
|
+
'"html":"<h1>{{ record.summary }}</h1>\\n<p>{{ record.name }}</p>\\n<p>{{ data.total | money(\\"CNY\\") }}</p>",'
|
|
337
|
+
'"code":"import flowbase\\n\\ndef run():\\n amount = flowbase.record.values.get(\\"amount\\") or 0\\n return {\\"total\\": amount}"}'
|
|
338
338
|
)),
|
|
339
|
-
Endpoint("print-templates.update", "flowbase_print_templates_update", "PUT", "/objects/{object_id}/print-templates/{template_id}", "metadata:write", "
|
|
339
|
+
Endpoint("print-templates.update", "flowbase_print_templates_update", "PUT", "/objects/{object_id}/print-templates/{template_id}", "metadata:write", "Partial update. Send changed fields plus baseRevision and baseUpdatedAt. Do not resend id, key, enabled, updatedAt, or draftRevision.", "flowbase print-templates update obj_xxx ptp_xxx --json-file template.json",
|
|
340
340
|
request_body=(
|
|
341
341
|
'{"baseRevision":4,"baseUpdatedAt":"2026-10-13T08:00:00+00:00",'
|
|
342
342
|
'"html":"<h1>{{ record.summary }}</h1>"}'
|
|
@@ -810,19 +810,23 @@ AUTHORING = {
|
|
|
810
810
|
"views.create": ["baseRevision", "name", "defaultFieldIds"],
|
|
811
811
|
"pages.create": ["baseRevision", "page"],
|
|
812
812
|
"records.create": ["values"],
|
|
813
|
+
"print-templates.create": ["name", "html"],
|
|
813
814
|
},
|
|
814
815
|
"draftRevision": {
|
|
815
816
|
"notReturnedBy": ["objects.get", "objects.list", "apps.get", "apps.list"],
|
|
816
817
|
"returnedBy": [
|
|
817
818
|
"views.list", "pages.list", "pages.get",
|
|
819
|
+
"print-templates.list", "print-templates.get",
|
|
818
820
|
"every metadata write response (draftRevision)",
|
|
819
821
|
],
|
|
820
|
-
"note": "Capture draftRevision from the last write or from views.list/pages.list before
|
|
822
|
+
"note": "Capture draftRevision from the last write or from views.list/pages.list/print-templates.list before writes.",
|
|
821
823
|
},
|
|
822
824
|
"readThenWrite": (
|
|
823
825
|
"For update of views/pages/dashboards/fields with constraints: get the snapshot, "
|
|
824
826
|
"edit it, set baseRevision from draftRevision. fields.update.constraints is a full replace; "
|
|
825
|
-
"sending {} wipes options."
|
|
827
|
+
"sending {} wipes options. print-templates.update is partial: send only changed fields, "
|
|
828
|
+
"baseRevision, and baseUpdatedAt copied from updatedAt. Resending id, key, enabled, "
|
|
829
|
+
"updatedAt, or draftRevision is rejected."
|
|
826
830
|
),
|
|
827
831
|
"recordsNeedReleasedMetadata": (
|
|
828
832
|
"On production, records.create/list/get use the released runtime, not the current draft. "
|
|
@@ -928,6 +932,18 @@ AUTHORING = {
|
|
|
928
932
|
"wrong": "dashboard date_range filter with no default",
|
|
929
933
|
"right": "Unset date_range defaults to last_30_days at runtime; set default.preset or omit the filter.",
|
|
930
934
|
},
|
|
935
|
+
{
|
|
936
|
+
"wrong": "print HTML {name} or {{ name }} or {{ record.客户名称 }}",
|
|
937
|
+
"right": "Use {{ record.<field key> }} with the key from objects get. See docs catalogs print-templates.",
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
"wrong": "print footer {{ page }} / {{ pages }}",
|
|
941
|
+
"right": "Page numbers are the literal tokens {page} and {pages}, only in header and footer.",
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
"wrong": "print-templates.update with the whole get document",
|
|
945
|
+
"right": "Partial update. Omit id, key, enabled, updatedAt, and draftRevision. Copy updatedAt to baseUpdatedAt.",
|
|
946
|
+
},
|
|
931
947
|
],
|
|
932
948
|
}
|
|
933
949
|
|
|
@@ -1269,6 +1285,7 @@ RESOURCE_ID_PREFIXES = {
|
|
|
1269
1285
|
"reg_": "page region", "cmp_": "page component",
|
|
1270
1286
|
"dsh_": "dashboard", "dcm_": "dashboard component", "rol_": "role",
|
|
1271
1287
|
"rec_": "record", "att_": "attachment", "emp_": "employee", "dep_": "department",
|
|
1288
|
+
"ptp_": "print template",
|
|
1272
1289
|
}
|
|
1273
1290
|
|
|
1274
1291
|
WORKFLOW_BRANCHES = {
|
|
@@ -1323,11 +1340,51 @@ DASHBOARD_RUNTIME = {
|
|
|
1323
1340
|
"example": "import flowbase\n\ndef run():\n return {\"type\": \"metric\", \"value\": 1}\n",
|
|
1324
1341
|
}
|
|
1325
1342
|
|
|
1343
|
+
PRINT_TEMPLATES = {
|
|
1344
|
+
"command": "flowbase docs catalogs print-templates",
|
|
1345
|
+
"readFirst": "objects get supplies field keys. print-templates list supplies draftRevision. print-templates get supplies html, code, header, footer, and updatedAt.",
|
|
1346
|
+
"placeholders": {
|
|
1347
|
+
"html": "Jinja2: {{ }} output, {% for %} loops, {% if %} conditions. An unknown name renders blank and still saves.",
|
|
1348
|
+
"record": "{{ record.<fieldKey> }} is display text. {{ record.values.<fieldKey> }} is the raw value. {{ record.summary }} is the record summary. Built-in id, summary, and values win over a field with the same key.",
|
|
1349
|
+
"keys": "fieldKey is the field key from objects get, never the label and never a fld_ id. Replace name and amount in the create example with keys from that object.",
|
|
1350
|
+
"data": "data is only the dict returned by run(). With no code, data is {}. {{ data.total }} stays blank unless run() returns total.",
|
|
1351
|
+
"other": "employee.name, employee.email, now (ISO string; format with the date filter), template.name, template.key.",
|
|
1352
|
+
"pageTokens": "{page} and {pages} are not Jinja. They work only in header and footer, as literal single-brace text. {{ page }} is erased before page numbers are applied. In the HTML body, {page} stays as the text {page}.",
|
|
1353
|
+
},
|
|
1354
|
+
"filters": {
|
|
1355
|
+
"money": "{{ record.values.amount | money(\"CNY\") }} thousands, 2 decimals, currency symbol. Use the raw value.",
|
|
1356
|
+
"money_upper": "{{ data.total | money_upper }} RMB amount in Chinese capitals.",
|
|
1357
|
+
"date": "{{ record.values.signed_on | date(\"YYYY-MM-DD HH:mm\") }} or {{ now | date(\"YYYY-MM-DD\") }}.",
|
|
1358
|
+
"rich": "{{ record.values.remark | rich }} sanitized HTML for a rich-text field.",
|
|
1359
|
+
"image": "{{ record.values.photo | image(width=\"40mm\") }} inlines a visible image attachment.",
|
|
1360
|
+
"qrcode": "{{ record.values.order_no | qrcode(size=\"25mm\") }}",
|
|
1361
|
+
"barcode": "{{ record.values.order_no | barcode(\"code128\", height=\"12mm\") }} code128 or ean13.",
|
|
1362
|
+
"nl2br": "{{ record.notes | nl2br }} turns newlines into <br>.",
|
|
1363
|
+
"removed": "safe, urlize, and xmlattr are unavailable. Output is HTML-escaped unless a filter above returns HTML.",
|
|
1364
|
+
},
|
|
1365
|
+
"code": {
|
|
1366
|
+
"shape": "Optional. import flowbase only. Define run() with no arguments and return a JSON object, max 2 MB.",
|
|
1367
|
+
"record": "flowbase.record.values[key] is raw; flowbase.record.display[key] is display text; flowbase.record.summary is the summary.",
|
|
1368
|
+
"related": "flowbase.records.related(relation, fields=None, order_by=None, limit=200, record=None). relation is a lookup field key, forward or reverse. Return line.display dicts so the template can use {{ line.<fieldKey> }}.",
|
|
1369
|
+
"get": "flowbase.records.get(object, record_id, fields=None) returns the same shape or None. object is an object key.",
|
|
1370
|
+
"query": "flowbase.query.records.count/group_by/timeseries/list match dashboard-runtime but have no inherit_filters argument. scope is own or omitted.",
|
|
1371
|
+
"approvals": "flowbase.approvals.history(record=None) returns up to 50 instances.",
|
|
1372
|
+
"example": "import flowbase\n\ndef run():\n record = flowbase.record\n lines = flowbase.records.related(\"lines\", fields=[\"product\", \"amount\"])\n return {\n \"total\": record.values.get(\"amount\") or 0,\n \"lines\": [line.display for line in lines],\n }\n",
|
|
1373
|
+
},
|
|
1374
|
+
"write": {
|
|
1375
|
+
"create": "Requires name and html. Other settings have defaults. New templates are enabled. key cannot be changed later. enabled is not a create/update field; use set-enabled.",
|
|
1376
|
+
"update": "Partial. Omitted fields stay. Copy updatedAt to baseUpdatedAt and draftRevision to baseRevision. Do not send id, key, enabled, updatedAt, or draftRevision.",
|
|
1377
|
+
"limits": "At most 20 templates per object. HTML 200 KB, code 100 KB, each header/footer slot 200 characters. A non-empty header needs top margin >= 10; a non-empty footer needs bottom margin >= 10.",
|
|
1378
|
+
},
|
|
1379
|
+
"verify": "The management CLI cannot render a template. get returns the source. A saved {{ }} that names the wrong key prints blank. There is no print-templates run command.",
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1326
1382
|
CATALOGS = {
|
|
1327
1383
|
"workflow-branches": WORKFLOW_BRANCHES,
|
|
1328
1384
|
"approval-rules": APPROVAL_RULES,
|
|
1329
1385
|
"page-buttons": PAGE_BUTTONS,
|
|
1330
1386
|
"dashboard-runtime": DASHBOARD_RUNTIME,
|
|
1387
|
+
"print-templates": PRINT_TEMPLATES,
|
|
1331
1388
|
"field-types": FIELD_TYPES,
|
|
1332
1389
|
"view-filters": VIEW_FILTERS,
|
|
1333
1390
|
"record-values": RECORD_VALUES,
|
|
@@ -8,7 +8,7 @@ GUIDE = {
|
|
|
8
8
|
"flowbase apps list;flowbase objects list:发现已有应用和对象,优先复用,不猜测 ID。",
|
|
9
9
|
"flowbase objects get <object_id>:读取字段 key、类型、约束和真实选项 ID;关联字段继续读取目标对象。",
|
|
10
10
|
"flowbase docs catalogs authoring;flowbase docs list;flowbase docs show <endpoint_key>:查请求和更新方式。",
|
|
11
|
-
"配置页面前读取 pages options、page-components、page-rules、page-buttons;配置流程前读取 workflow-nodes、workflow-branches 或 approval-rules。",
|
|
11
|
+
"配置页面前读取 pages options、page-components、page-rules、page-buttons;配置流程前读取 workflow-nodes、workflow-branches 或 approval-rules;配置打印模板前读取 print-templates。",
|
|
12
12
|
"先 get 再修改,保留未修改的配置。按目录转换更新 envelope;从最近读取或写入返回值获取 draftRevision,作为 baseRevision。",
|
|
13
13
|
"写后回读验证。records create 返回 recordId,再 records get;values 使用字段 key,选择和关联使用真实 ID。",
|
|
14
14
|
"遍历列表的所有分页。records list --view-id <view_id> 验证特定视图,records get 获取完整记录。",
|
|
@@ -29,7 +29,7 @@ GUIDE = {
|
|
|
29
29
|
],
|
|
30
30
|
"limits": [
|
|
31
31
|
"docs/guide 离线可读;业务调用需要权限和已启用环境。",
|
|
32
|
-
"
|
|
32
|
+
"页面规则求值不是浏览器渲染;仪表盘输出验证不是图表视觉验证。打印模板 get 返回源码,管理 CLI 不能生成打印内容;写错的 {{ }} 会保存成功并打印成空白。",
|
|
33
33
|
"组织候选只读;不能通过管理 CLI 替员工做审批决定。",
|
|
34
34
|
"删除对象/字段/页面是逻辑删除;没有物理 purge,审计与回收站会保留。",
|
|
35
35
|
],
|
|
@@ -23,10 +23,25 @@ class AutonomyTest(unittest.TestCase):
|
|
|
23
23
|
self.assertIn('低代码平台', guide['product'])
|
|
24
24
|
self.assertTrue(any('objects get' in item for item in guide['workflow']))
|
|
25
25
|
self.assertEqual(code, 0)
|
|
26
|
+
self.assertTrue(any('print-templates' in item for item in guide['workflow']))
|
|
26
27
|
code, out, _ = self.invoke(['docs', 'catalogs'])
|
|
27
|
-
self.assertTrue({'page-buttons', 'workflow-branches', 'approval-rules', 'dashboard-runtime'}.issubset(json.loads(out)))
|
|
28
|
+
self.assertTrue({'page-buttons', 'workflow-branches', 'approval-rules', 'dashboard-runtime', 'print-templates'}.issubset(json.loads(out)))
|
|
28
29
|
request.assert_not_called()
|
|
29
30
|
|
|
31
|
+
def test_print_template_catalog_separates_field_and_page_placeholders(self):
|
|
32
|
+
code, out, err = self.invoke(['docs', 'catalogs', 'print-templates'])
|
|
33
|
+
self.assertEqual(code, 0, err)
|
|
34
|
+
catalog = json.loads(out)
|
|
35
|
+
placeholders = catalog['placeholders']
|
|
36
|
+
self.assertIn('{{ record.<fieldKey> }}', placeholders['record'])
|
|
37
|
+
self.assertIn('objects get', placeholders['keys'])
|
|
38
|
+
self.assertIn('{page}', placeholders['pageTokens'])
|
|
39
|
+
self.assertIn('{{ page }}', placeholders['pageTokens'])
|
|
40
|
+
self.assertIn('header and footer', placeholders['pageTokens'])
|
|
41
|
+
self.assertIn('no inherit_filters', catalog['code']['query'])
|
|
42
|
+
self.assertIn('baseUpdatedAt', catalog['write']['update'])
|
|
43
|
+
self.assertIn('cannot render', catalog['verify'])
|
|
44
|
+
|
|
30
45
|
def test_write_disconnect_is_not_retried_or_reported_as_definite_failure(self):
|
|
31
46
|
for error in (RemoteDisconnected('connection closed'), TimeoutError('timed out'), URLError('fbk_secret rejected')):
|
|
32
47
|
with self.subTest(error=error), patch('flowbase_cli.main.urlopen', side_effect=error) as request:
|
|
@@ -334,6 +334,7 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
334
334
|
prefixes = json.loads(stdout.getvalue())
|
|
335
335
|
self.assertEqual(prefixes["reg_"], "page region")
|
|
336
336
|
self.assertEqual(prefixes["cmp_"], "page component")
|
|
337
|
+
self.assertEqual(prefixes["ptp_"], "print template")
|
|
337
338
|
|
|
338
339
|
def test_docs_list_marks_body_and_confirmation(self):
|
|
339
340
|
stdout = io.StringIO()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|