flowbase-cli 0.1.0__tar.gz → 0.1.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbase-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Command line client for the FlowBase OpenAPI
5
5
  Author: DeepFlowAI
6
6
  License: Apache-2.0
@@ -23,7 +23,9 @@ export FLOWBASE_API_KEY=fbk_xxx
23
23
 
24
24
  flowbase objects list
25
25
  flowbase records create obj_xxx --json '{"values":{"name":"Example"}}'
26
+ flowbase changes commit --message "Add customer field" --yes
27
+ flowbase changes release --message "Release customer field" --yes
26
28
  flowbase docs list
27
29
  ```
28
30
 
29
- `--host` selects the environment through its Host. The CLI never accepts a separate environment override and never stores API keys on disk. See `flowbase --help` and `flowbase docs list` for the complete command set.
31
+ `--host` selects the environment through its Host. Version commands are limited to the current enabled test environment; release can succeed only when its current version meets FlowBase's fast-forward checks. The CLI never accepts a separate environment override and never stores API keys on disk. See `flowbase --help` and `flowbase docs list` for the complete command set.
@@ -0,0 +1,22 @@
1
+ # FlowBase CLI
2
+
3
+ Install from this checkout:
4
+
5
+ ```bash
6
+ python -m pip install ./packages/flowbase-cli
7
+ ```
8
+
9
+ Configure an environment Base URL and an API key:
10
+
11
+ ```bash
12
+ export FLOWBASE_HOST=https://flowbase-test-a.deepflowagent.com
13
+ export FLOWBASE_API_KEY=fbk_xxx
14
+
15
+ flowbase objects list
16
+ flowbase records create obj_xxx --json '{"values":{"name":"Example"}}'
17
+ flowbase changes commit --message "Add customer field" --yes
18
+ flowbase changes release --message "Release customer field" --yes
19
+ flowbase docs list
20
+ ```
21
+
22
+ `--host` selects the environment through its Host. Version commands are limited to the current enabled test environment; release can succeed only when its current version meets FlowBase's fast-forward checks. The CLI never accepts a separate environment override and never stores API keys on disk. See `flowbase --help` and `flowbase docs list` for the complete command set.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flowbase-cli"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Command line client for the FlowBase OpenAPI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """FlowBase OpenAPI CLI."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.1"
@@ -27,6 +27,8 @@ ENDPOINTS = (
27
27
  Endpoint("fields.update", "flowbase_fields_update", "PUT", "/objects/{object_id}/fields/{field_id}", "metadata:write", "Update a field draft.", "flowbase fields update obj_xxx fld_xxx --label Name --description 'Customer name'"),
28
28
  Endpoint("fields.delete", "flowbase_fields_delete", "DELETE", "/objects/{object_id}/fields/{field_id}", "metadata:write", "Logically delete a field.", "flowbase fields delete obj_xxx fld_xxx --yes", True),
29
29
  Endpoint("fields.restore", "flowbase_fields_restore", "POST", "/objects/{object_id}/fields/{field_id}/restore", "metadata:write", "Restore a field.", "flowbase fields restore obj_xxx fld_xxx --yes", True),
30
+ Endpoint("changes.commit", "flowbase_changes_commit", "POST", "/changes/commit", "metadata:write", "Commit current test-environment drafts.", "flowbase changes commit --message 'Add customer field' --yes", True),
31
+ Endpoint("changes.release", "flowbase_changes_release", "POST", "/changes/release", "metadata:write", "Release the current test-environment version.", "flowbase changes release --message 'Release customer field' --yes", True),
30
32
  Endpoint("records.list", "flowbase_records_list", "GET", "/objects/{object_id}/records", "records:read", "List records.", "flowbase records list obj_xxx --page 1"),
31
33
  Endpoint("records.get", "flowbase_records_get", "GET", "/objects/{object_id}/records/{record_id}", "records:read", "Get a record.", "flowbase records get obj_xxx rec_xxx"),
32
34
  Endpoint("records.create", "flowbase_records_create", "POST", "/objects/{object_id}/records", "records:write", "Create a record.", "flowbase records create obj_xxx --json-file record.json"),
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import argparse
5
+ from decimal import Decimal
5
6
  import json
6
7
  import os
7
8
  import sys
@@ -49,7 +50,7 @@ def load_json_payload(json_text: str | None, json_file: str | None) -> dict[str,
49
50
  except OSError as exc:
50
51
  raise CliError(f"Cannot read JSON file: {exc}") from exc
51
52
  try:
52
- payload = json.loads(source)
53
+ payload = json.loads(source, parse_float=Decimal)
53
54
  except json.JSONDecodeError as exc:
54
55
  raise CliError(f"Invalid JSON at line {exc.lineno}, column {exc.colno}: {exc.msg}") from exc
55
56
  if not isinstance(payload, dict):
@@ -99,7 +100,7 @@ class OpenApiClient:
99
100
  headers = {"Accept": "application/json", "Authorization": f"Bearer {self.api_key}"}
100
101
  data = None
101
102
  if body is not None:
102
- data = json.dumps(body, ensure_ascii=False).encode("utf-8")
103
+ data = exact_json_dumps(body).encode("utf-8")
103
104
  headers["Content-Type"] = "application/json"
104
105
  request = Request(url, data=data, headers=headers, method=endpoint.method)
105
106
  try:
@@ -127,6 +128,33 @@ def _error_detail(payload: str) -> str:
127
128
  return json.dumps(parsed, ensure_ascii=False)
128
129
 
129
130
 
131
+ def exact_json_dumps(value: Any) -> str:
132
+ if value is None:
133
+ return "null"
134
+ if value is True:
135
+ return "true"
136
+ if value is False:
137
+ return "false"
138
+ if isinstance(value, str):
139
+ return json.dumps(value, ensure_ascii=False)
140
+ if isinstance(value, Decimal):
141
+ if not value.is_finite():
142
+ raise CliError("JSON numbers must be finite.")
143
+ return format(value, "f")
144
+ if isinstance(value, int):
145
+ return str(value)
146
+ if isinstance(value, float):
147
+ raise CliError("Binary floating-point values are not accepted.")
148
+ if isinstance(value, list):
149
+ return "[" + ",".join(exact_json_dumps(item) for item in value) + "]"
150
+ if isinstance(value, dict):
151
+ return "{" + ",".join(
152
+ f"{json.dumps(str(key), ensure_ascii=False)}:{exact_json_dumps(item)}"
153
+ for key, item in value.items()
154
+ ) + "}"
155
+ raise CliError(f"Unsupported JSON value: {type(value).__name__}")
156
+
157
+
130
158
  def print_output(data: bytes, *, pretty: bool, raw: bool) -> None:
131
159
  text = data.decode("utf-8", errors="replace")
132
160
  if raw:
@@ -240,6 +268,10 @@ def handle_fields(args: argparse.Namespace) -> int:
240
268
  return run_request(args, f"fields.{action}", path_params=path)
241
269
 
242
270
 
271
+ def handle_changes(args: argparse.Namespace) -> int:
272
+ return run_request(args, f"changes.{args.changes_action}", body={"message": args.message})
273
+
274
+
243
275
  def handle_records(args: argparse.Namespace) -> int:
244
276
  action = args.records_action
245
277
  path = {"object_id": args.object_id}
@@ -322,6 +354,14 @@ def build_parser() -> argparse.ArgumentParser:
322
354
  add_yes(item)
323
355
  fields.set_defaults(handler=handle_fields)
324
356
 
357
+ changes = groups.add_parser("changes", help="Commit or release eligible test-environment metadata versions.")
358
+ change_actions = changes.add_subparsers(dest="changes_action", required=True)
359
+ for action in ("commit", "release"):
360
+ item = change_actions.add_parser(action)
361
+ item.add_argument("--message", required=True)
362
+ add_yes(item)
363
+ changes.set_defaults(handler=handle_changes)
364
+
325
365
  records = groups.add_parser("records", help="Manage business records.")
326
366
  record_actions = records.add_subparsers(dest="records_action", required=True)
327
367
  record_list = record_actions.add_parser("list")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbase-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Command line client for the FlowBase OpenAPI
5
5
  Author: DeepFlowAI
6
6
  License: Apache-2.0
@@ -23,7 +23,9 @@ export FLOWBASE_API_KEY=fbk_xxx
23
23
 
24
24
  flowbase objects list
25
25
  flowbase records create obj_xxx --json '{"values":{"name":"Example"}}'
26
+ flowbase changes commit --message "Add customer field" --yes
27
+ flowbase changes release --message "Release customer field" --yes
26
28
  flowbase docs list
27
29
  ```
28
30
 
29
- `--host` selects the environment through its Host. The CLI never accepts a separate environment override and never stores API keys on disk. See `flowbase --help` and `flowbase docs list` for the complete command set.
31
+ `--host` selects the environment through its Host. Version commands are limited to the current enabled test environment; release can succeed only when its current version meets FlowBase's fast-forward checks. The CLI never accepts a separate environment override and never stores API keys on disk. See `flowbase --help` and `flowbase docs list` for the complete command set.
@@ -3,12 +3,20 @@ from __future__ import annotations
3
3
  import io
4
4
  import json
5
5
  import unittest
6
+ from decimal import Decimal
6
7
  from pathlib import Path
7
8
  from tempfile import TemporaryDirectory
8
9
  from unittest.mock import patch
9
10
 
10
11
  from flowbase_cli.endpoints import ENDPOINTS
11
- from flowbase_cli.main import CliError, OpenApiClient, api_root, load_json_payload, main
12
+ from flowbase_cli.main import (
13
+ CliError,
14
+ OpenApiClient,
15
+ api_root,
16
+ exact_json_dumps,
17
+ load_json_payload,
18
+ main,
19
+ )
12
20
 
13
21
 
14
22
  class FlowBaseCliTest(unittest.TestCase):
@@ -33,6 +41,18 @@ class FlowBaseCliTest(unittest.TestCase):
33
41
  path.write_text('{"label":"客户"}', encoding="utf-8")
34
42
  self.assertEqual(load_json_payload(None, str(path)), {"label": "客户"})
35
43
 
44
+ def test_decimal_payload_preserves_json_number_text(self):
45
+ payload = load_json_payload(
46
+ '{"values":{"amount":12345678901234.5678}}', None
47
+ )
48
+ self.assertEqual(
49
+ payload["values"]["amount"], Decimal("12345678901234.5678")
50
+ )
51
+ self.assertEqual(
52
+ exact_json_dumps(payload),
53
+ '{"values":{"amount":12345678901234.5678}}',
54
+ )
55
+
36
56
  def test_missing_key_is_rejected_before_network(self):
37
57
  client = OpenApiClient("https://example.com", None)
38
58
  with patch("flowbase_cli.main.urlopen") as mocked:
@@ -53,6 +73,7 @@ class FlowBaseCliTest(unittest.TestCase):
53
73
  self.assertEqual(code, 0)
54
74
  payload = json.loads(stdout.getvalue())
55
75
  self.assertTrue(any(item["key"] == "records.create" for item in payload))
76
+ self.assertTrue(any(item["key"] == "changes.release" for item in payload))
56
77
 
57
78
  def test_object_create_requires_business_description(self):
58
79
  stderr = io.StringIO()
@@ -68,6 +89,13 @@ class FlowBaseCliTest(unittest.TestCase):
68
89
  self.assertEqual(code, 1)
69
90
  self.assertIn("without --yes", stderr.getvalue())
70
91
 
92
+ def test_noninteractive_commit_requires_yes(self):
93
+ stderr = io.StringIO()
94
+ with patch("sys.stderr", stderr), patch("sys.stdin.isatty", return_value=False):
95
+ code = main(["--api-key", "fbk_test", "changes", "commit", "--message", "Add customer field"])
96
+ self.assertEqual(code, 1)
97
+ self.assertIn("without --yes", stderr.getvalue())
98
+
71
99
 
72
100
  if __name__ == "__main__":
73
101
  unittest.main()
@@ -1,20 +0,0 @@
1
- # FlowBase CLI
2
-
3
- Install from this checkout:
4
-
5
- ```bash
6
- python -m pip install ./packages/flowbase-cli
7
- ```
8
-
9
- Configure an environment Base URL and an API key:
10
-
11
- ```bash
12
- export FLOWBASE_HOST=https://flowbase-test-a.deepflowagent.com
13
- export FLOWBASE_API_KEY=fbk_xxx
14
-
15
- flowbase objects list
16
- flowbase records create obj_xxx --json '{"values":{"name":"Example"}}'
17
- flowbase docs list
18
- ```
19
-
20
- `--host` selects the environment through its Host. The CLI never accepts a separate environment override and never stores API keys on disk. See `flowbase --help` and `flowbase docs list` for the complete command set.
File without changes