maybeai-sheet-cli 0.19.2__tar.gz → 0.20.0__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.
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/PKG-INFO +17 -2
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/README.md +16 -1
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/pyproject.toml +1 -1
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/db_table.py +57 -6
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/style.py +252 -36
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/endpoints.py +3 -2
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_cli.py +5 -1
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_endpoints.py +7 -1
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_phase2_cli.py +277 -7
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_two_engine_cli.py +62 -7
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/.gitignore +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/__init__.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/chart_config.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/chart_style_summary.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/cli.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/client.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/__init__.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/_phase2.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/cell.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/chart.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/column.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/dashboard.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/excel_table.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/excel_worksheet.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/file.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/formula.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/history.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/image.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/media.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/pivot.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/range.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/raw.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/row.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/share.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/sheet.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/table.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/workbook.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/worksheet.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/config.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/dashboard_interactions.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/dashboard_plan.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/errors.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/formatters.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/models/__init__.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/models/share.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/models/sheet.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/models/workbook.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/options.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/protocol.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/resolver.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/table_resolver.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/update.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/build_fixtures.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/flat-records.xlsx +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/mixed-engine.xlsx +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/multi-table-layout.xlsx +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/rows.json +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/simple-grid.xlsx +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/fixtures/benchmarks/simple-values.json +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_benchmark_runner.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_dashboard_plan.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_formatters.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_resolver.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_update.py +0 -0
- {maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maybeai-sheet-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
4
4
|
Summary: CLI for common MaybeAI spreadsheet operations
|
|
5
5
|
Project-URL: Homepage, https://github.com/OmniMCP-AI/maybeai-sheet-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/OmniMCP-AI/maybeai-sheet-cli
|
|
@@ -641,7 +641,7 @@ mbs db-table insert --doc-id abc123 --name orders-large --rows rows.json # API
|
|
|
641
641
|
mbs db-table create --doc-id abc123 --name Orders --rows orders.json # API
|
|
642
642
|
mbs db-table create-from-query --doc-id abc123 --name OrderSummary --sql-file order_summary.sql # API
|
|
643
643
|
mbs db-table create-from-range --doc-id target123 --name R_OrderLines_Store1 --source-doc-id source123 --worksheet-name "1店" --range A2:AR423 --header-row 0 --use-header-names --if-exists adopt --verify # API
|
|
644
|
-
mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json #
|
|
644
|
+
mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json # API
|
|
645
645
|
mbs db-table update --doc-id abc123 --name Orders --row-index 42 --rows row_patch.json # PLAN
|
|
646
646
|
mbs db-table update --doc-id abc123 --name Orders --where '{"status":"open"}' --rows status_patch.json # PLAN
|
|
647
647
|
mbs db-table update --doc-id abc123 --name Orders --column-index 3 --column-name status # PLAN
|
|
@@ -751,6 +751,21 @@ is also CLI-composed: it reads a source worksheet range, reshapes header/data
|
|
|
751
751
|
rows, then creates a PG/SheetTable on the target workbook. Use a keyset-style
|
|
752
752
|
backend contract for deeper pagination when it becomes available.
|
|
753
753
|
|
|
754
|
+
`db-table update` currently supports keyed row updates through the routed
|
|
755
|
+
`update_range_by_lookup` backend contract:
|
|
756
|
+
|
|
757
|
+
```bash
|
|
758
|
+
mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
The `--key` column must be present in each row object and is used to match target
|
|
762
|
+
rows, for example `order_id`, `sku`, or `id`. Patch files may include only the
|
|
763
|
+
columns that should change; omitted columns are left unchanged. Blank values
|
|
764
|
+
overwrite existing values only when `--override` is passed. Positional updates
|
|
765
|
+
with `--row-index`, column-level updates with `--column-index` / `--column-name`,
|
|
766
|
+
and JSON-filter updates with `--where` remain planned until the backend exposes a
|
|
767
|
+
stable native db-table contract for those modes.
|
|
768
|
+
|
|
754
769
|
Refactor planning is tracked separately from this README:
|
|
755
770
|
`docs/superpowers/plans/2026-07-01-mbs-two-engine-command-refactor.md`.
|
|
756
771
|
|
|
@@ -617,7 +617,7 @@ mbs db-table insert --doc-id abc123 --name orders-large --rows rows.json # API
|
|
|
617
617
|
mbs db-table create --doc-id abc123 --name Orders --rows orders.json # API
|
|
618
618
|
mbs db-table create-from-query --doc-id abc123 --name OrderSummary --sql-file order_summary.sql # API
|
|
619
619
|
mbs db-table create-from-range --doc-id target123 --name R_OrderLines_Store1 --source-doc-id source123 --worksheet-name "1店" --range A2:AR423 --header-row 0 --use-header-names --if-exists adopt --verify # API
|
|
620
|
-
mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json #
|
|
620
|
+
mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json # API
|
|
621
621
|
mbs db-table update --doc-id abc123 --name Orders --row-index 42 --rows row_patch.json # PLAN
|
|
622
622
|
mbs db-table update --doc-id abc123 --name Orders --where '{"status":"open"}' --rows status_patch.json # PLAN
|
|
623
623
|
mbs db-table update --doc-id abc123 --name Orders --column-index 3 --column-name status # PLAN
|
|
@@ -727,6 +727,21 @@ is also CLI-composed: it reads a source worksheet range, reshapes header/data
|
|
|
727
727
|
rows, then creates a PG/SheetTable on the target workbook. Use a keyset-style
|
|
728
728
|
backend contract for deeper pagination when it becomes available.
|
|
729
729
|
|
|
730
|
+
`db-table update` currently supports keyed row updates through the routed
|
|
731
|
+
`update_range_by_lookup` backend contract:
|
|
732
|
+
|
|
733
|
+
```bash
|
|
734
|
+
mbs db-table update --doc-id abc123 --name Orders --key order_id --rows orders_patch.json
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
The `--key` column must be present in each row object and is used to match target
|
|
738
|
+
rows, for example `order_id`, `sku`, or `id`. Patch files may include only the
|
|
739
|
+
columns that should change; omitted columns are left unchanged. Blank values
|
|
740
|
+
overwrite existing values only when `--override` is passed. Positional updates
|
|
741
|
+
with `--row-index`, column-level updates with `--column-index` / `--column-name`,
|
|
742
|
+
and JSON-filter updates with `--where` remain planned until the backend exposes a
|
|
743
|
+
stable native db-table contract for those modes.
|
|
744
|
+
|
|
730
745
|
Refactor planning is tracked separately from this README:
|
|
731
746
|
`docs/superpowers/plans/2026-07-01-mbs-two-engine-command-refactor.md`.
|
|
732
747
|
|
{maybeai_sheet_cli-0.19.2 → maybeai_sheet_cli-0.20.0}/src/maybeai_sheet/commands/db_table.py
RENAMED
|
@@ -1162,9 +1162,6 @@ import data:
|
|
|
1162
1162
|
DB_TABLE_UPDATE_EXAMPLE = """Example:
|
|
1163
1163
|
|
|
1164
1164
|
mbs db-table update --doc-id <doc_id> --name Orders --key order_id --rows orders_patch.json
|
|
1165
|
-
mbs db-table update --doc-id <doc_id> --name Orders --row-index 42 --rows row_patch.json
|
|
1166
|
-
mbs db-table update --doc-id <doc_id> --name Orders --where '{"status":"open"}' --rows status_patch.json
|
|
1167
|
-
mbs db-table update --doc-id <doc_id> --name Orders --column-index 3 --column-name status
|
|
1168
1165
|
|
|
1169
1166
|
`orders_patch.json` should contain row objects keyed by --key. Positional
|
|
1170
1167
|
updates use 1-based --row-index and --column-index when the backend exposes a
|
|
@@ -1574,12 +1571,16 @@ def create_from_range(
|
|
|
1574
1571
|
|
|
1575
1572
|
@app.command(
|
|
1576
1573
|
"update",
|
|
1577
|
-
help="
|
|
1574
|
+
help="Update PG/SheetTable-backed rows by lookup key from JSON row data.",
|
|
1578
1575
|
epilog=DB_TABLE_UPDATE_EXAMPLE,
|
|
1579
1576
|
)
|
|
1580
1577
|
def update(
|
|
1578
|
+
ctx: typer.Context,
|
|
1581
1579
|
doc_id: Optional[str] = typer.Option(None, "--doc-id", help="MaybeAI document ID."),
|
|
1580
|
+
url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
|
|
1581
|
+
uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
|
|
1582
1582
|
name: Optional[str] = typer.Option(None, "--name", help="PG-backed table name."),
|
|
1583
|
+
backend_id: Optional[str] = typer.Option(None, "--backend-id", help="Advanced backend table id."),
|
|
1583
1584
|
key: Optional[str] = typer.Option(None, "--key", help="Unique key column for matching rows."),
|
|
1584
1585
|
row_index: Optional[int] = typer.Option(None, "--row-index", min=1, help="1-based SheetTable row index to update."),
|
|
1585
1586
|
column_index: Optional[int] = typer.Option(None, "--column-index", min=1, help="1-based SheetTable column index to update."),
|
|
@@ -1593,10 +1594,60 @@ def update(
|
|
|
1593
1594
|
resolve_path=True,
|
|
1594
1595
|
help="Path to JSON rows payload.",
|
|
1595
1596
|
),
|
|
1597
|
+
override: bool = typer.Option(False, "--override", help="Allow blank values in --rows to overwrite existing cells."),
|
|
1598
|
+
skip_recalculation: bool = typer.Option(False, "--skip-recalculation", help="Skip immediate recalculation."),
|
|
1599
|
+
output: Optional[str] = output_option(),
|
|
1600
|
+
verbose: Optional[bool] = verbose_option(),
|
|
1601
|
+
timeout: Optional[float] = timeout_option(),
|
|
1596
1602
|
) -> None:
|
|
1597
|
-
_ = (doc_id, name, key, row_index, column_index, column_name, where, rows)
|
|
1598
1603
|
try:
|
|
1599
|
-
|
|
1604
|
+
if row_index is not None or column_index is not None or column_name is not None or where is not None:
|
|
1605
|
+
raise UsageError(
|
|
1606
|
+
"db-table update currently supports keyed row updates only. "
|
|
1607
|
+
"Use --key with --rows; --row-index, --column-index, --column-name, and --where are planned."
|
|
1608
|
+
)
|
|
1609
|
+
if not key or not key.strip():
|
|
1610
|
+
raise UsageError("Provide --key for db-table update.")
|
|
1611
|
+
if rows is None:
|
|
1612
|
+
raise UsageError("Provide --rows for db-table update.")
|
|
1613
|
+
state = _state(
|
|
1614
|
+
ctx,
|
|
1615
|
+
doc_id=doc_id,
|
|
1616
|
+
url=url,
|
|
1617
|
+
uri=uri,
|
|
1618
|
+
output=output,
|
|
1619
|
+
verbose=verbose,
|
|
1620
|
+
timeout=timeout,
|
|
1621
|
+
)
|
|
1622
|
+
workbook_target = resolve_target(state)
|
|
1623
|
+
client = MaybeAIClient(state)
|
|
1624
|
+
resolved, target = _resolve_db_table(
|
|
1625
|
+
client=client,
|
|
1626
|
+
target=workbook_target,
|
|
1627
|
+
name=name,
|
|
1628
|
+
backend_id=backend_id,
|
|
1629
|
+
)
|
|
1630
|
+
endpoint = "/api/v1/excel/update_range_by_lookup"
|
|
1631
|
+
response = client.post_json(
|
|
1632
|
+
endpoint,
|
|
1633
|
+
{
|
|
1634
|
+
"uri": target.uri,
|
|
1635
|
+
"worksheet_name": resolved.worksheet_name,
|
|
1636
|
+
"data": _load_rows(rows),
|
|
1637
|
+
"on": [key.strip()],
|
|
1638
|
+
"override": override,
|
|
1639
|
+
"skip_recalculation": skip_recalculation,
|
|
1640
|
+
},
|
|
1641
|
+
)
|
|
1642
|
+
render_output(
|
|
1643
|
+
build_command_output(
|
|
1644
|
+
endpoint=endpoint,
|
|
1645
|
+
target=target,
|
|
1646
|
+
result=response,
|
|
1647
|
+
metadata=get_endpoint_metadata("db-table.update") if state.verbose else None,
|
|
1648
|
+
),
|
|
1649
|
+
state.output,
|
|
1650
|
+
)
|
|
1600
1651
|
except Exception as error: # pragma: no cover - CLI boundary
|
|
1601
1652
|
handle_cli_error(error)
|
|
1602
1653
|
|
|
@@ -51,6 +51,28 @@ _BEAUTIFY_DEFAULTS: dict[str, object] = {
|
|
|
51
51
|
"preset": "clean_data_table",
|
|
52
52
|
"hideGridlines": True,
|
|
53
53
|
}
|
|
54
|
+
_BEAUTIFY_BODY_BANDING: dict[str, object] = {
|
|
55
|
+
"enabled": True,
|
|
56
|
+
"oddRowBackground": "#FFFFFF",
|
|
57
|
+
"evenRowBackground": "#F8FAFC",
|
|
58
|
+
}
|
|
59
|
+
_BEAUTIFY_COLUMN_POLICY: dict[str, object] = {
|
|
60
|
+
"backgroundBySemanticType": False,
|
|
61
|
+
"allowFontColorBySemanticType": True,
|
|
62
|
+
"allowFormatBySemanticType": True,
|
|
63
|
+
"allowAlignmentBySemanticType": True,
|
|
64
|
+
}
|
|
65
|
+
_BEAUTIFY_CONFIG_KEYS = frozenset(
|
|
66
|
+
{
|
|
67
|
+
"defaults",
|
|
68
|
+
"layout",
|
|
69
|
+
"bodyBanding",
|
|
70
|
+
"columnPolicy",
|
|
71
|
+
"headerStyle",
|
|
72
|
+
"categoryStyles",
|
|
73
|
+
"sampling",
|
|
74
|
+
}
|
|
75
|
+
)
|
|
54
76
|
_BEAUTIFY_SAMPLE_RANGE = "A1:ZZ20"
|
|
55
77
|
_BEAUTIFY_HEADER_STYLE: dict[str, object] = {
|
|
56
78
|
"bold": True,
|
|
@@ -78,47 +100,40 @@ _BEAUTIFY_TEXT_STYLE: dict[str, object] = {
|
|
|
78
100
|
"format": "text",
|
|
79
101
|
"horizontal": "left",
|
|
80
102
|
"vertical": "middle",
|
|
81
|
-
"bg_color": "#FFFFFF",
|
|
82
103
|
}
|
|
83
104
|
_BEAUTIFY_DATE_STYLE: dict[str, object] = {
|
|
84
105
|
"format": "date",
|
|
85
106
|
"format_code": "yyyy-mm-dd",
|
|
86
107
|
"horizontal": "center",
|
|
87
|
-
"bg_color": "#F8FAFC",
|
|
88
108
|
}
|
|
89
109
|
_BEAUTIFY_CURRENCY_STYLE: dict[str, object] = {
|
|
90
110
|
"format": "currency",
|
|
91
111
|
"format_code": "$#,##0.00",
|
|
92
112
|
"horizontal": "right",
|
|
93
113
|
"font_color": "#065F46",
|
|
94
|
-
"bg_color": "#ECFDF5",
|
|
95
114
|
}
|
|
96
115
|
_BEAUTIFY_PERCENT_STYLE: dict[str, object] = {
|
|
97
116
|
"format": "percent",
|
|
98
117
|
"format_code": "0.00%",
|
|
99
118
|
"horizontal": "right",
|
|
100
119
|
"font_color": "#1D4ED8",
|
|
101
|
-
"bg_color": "#EFF6FF",
|
|
102
120
|
}
|
|
103
121
|
_BEAUTIFY_INTEGER_STYLE: dict[str, object] = {
|
|
104
122
|
"format": "integer",
|
|
105
123
|
"format_code": "#,##0",
|
|
106
124
|
"horizontal": "right",
|
|
107
125
|
"font_color": "#581C87",
|
|
108
|
-
"bg_color": "#F5F3FF",
|
|
109
126
|
}
|
|
110
127
|
_BEAUTIFY_NUMBER_STYLE: dict[str, object] = {
|
|
111
128
|
"format": "number",
|
|
112
129
|
"format_code": "0.00",
|
|
113
130
|
"horizontal": "right",
|
|
114
131
|
"font_color": "#374151",
|
|
115
|
-
"bg_color": "#F9FAFB",
|
|
116
132
|
}
|
|
117
133
|
_BEAUTIFY_STATUS_STYLE: dict[str, object] = {
|
|
118
134
|
"format": "text",
|
|
119
135
|
"horizontal": "left",
|
|
120
136
|
"font_color": "#92400E",
|
|
121
|
-
"bg_color": "#FEF3C7",
|
|
122
137
|
}
|
|
123
138
|
_BEAUTIFY_STYLE_BY_CATEGORY: dict[str, dict[str, object]] = {
|
|
124
139
|
"text": _BEAUTIFY_TEXT_STYLE,
|
|
@@ -129,6 +144,47 @@ _BEAUTIFY_STYLE_BY_CATEGORY: dict[str, dict[str, object]] = {
|
|
|
129
144
|
"number": _BEAUTIFY_NUMBER_STYLE,
|
|
130
145
|
"status": _BEAUTIFY_STATUS_STYLE,
|
|
131
146
|
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _default_beautify_style_policy() -> dict[str, object]:
|
|
150
|
+
return {
|
|
151
|
+
"defaults": dict(_BEAUTIFY_DEFAULTS),
|
|
152
|
+
"bodyBanding": dict(_BEAUTIFY_BODY_BANDING),
|
|
153
|
+
"columnPolicy": dict(_BEAUTIFY_COLUMN_POLICY),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _merge_dict(base: dict[str, object], override: object, *, option: str) -> dict[str, object]:
|
|
158
|
+
if override is None:
|
|
159
|
+
return dict(base)
|
|
160
|
+
if not isinstance(override, dict):
|
|
161
|
+
raise UsageError(f"{option} must be a JSON object.")
|
|
162
|
+
merged = dict(base)
|
|
163
|
+
merged.update(override)
|
|
164
|
+
return merged
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _beautify_config(config_path: Path | None) -> tuple[dict[str, object] | None, str | None]:
|
|
168
|
+
if config_path is None:
|
|
169
|
+
return None, None
|
|
170
|
+
config = load_json_object_argument(config_path, option="--config")
|
|
171
|
+
unsupported = sorted(set(config) - _BEAUTIFY_CONFIG_KEYS)
|
|
172
|
+
if unsupported:
|
|
173
|
+
raise UsageError(f"--config contains unsupported keys: {', '.join(unsupported)}.")
|
|
174
|
+
return config, str(config_path)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _beautify_style_policy(config: dict[str, object] | None = None) -> dict[str, object]:
|
|
178
|
+
policy = _default_beautify_style_policy()
|
|
179
|
+
if not config:
|
|
180
|
+
return policy
|
|
181
|
+
if "defaults" in config:
|
|
182
|
+
policy["defaults"] = _merge_dict(policy["defaults"], config["defaults"], option="--config defaults") # type: ignore[arg-type]
|
|
183
|
+
if "bodyBanding" in config:
|
|
184
|
+
policy["bodyBanding"] = _merge_dict(policy["bodyBanding"], config["bodyBanding"], option="--config bodyBanding") # type: ignore[arg-type]
|
|
185
|
+
if "columnPolicy" in config:
|
|
186
|
+
policy["columnPolicy"] = _merge_dict(policy["columnPolicy"], config["columnPolicy"], option="--config columnPolicy") # type: ignore[arg-type]
|
|
187
|
+
return policy
|
|
132
188
|
_BEAUTIFY_KEYWORDS: dict[str, tuple[str, ...]] = {
|
|
133
189
|
"date": (
|
|
134
190
|
"date",
|
|
@@ -580,6 +636,39 @@ def _beautify_workbook_payload(
|
|
|
580
636
|
}
|
|
581
637
|
|
|
582
638
|
|
|
639
|
+
def _is_styleable_worksheet(worksheet: dict[str, object]) -> bool:
|
|
640
|
+
worksheet_name = _worksheet_name(worksheet)
|
|
641
|
+
if not worksheet_name:
|
|
642
|
+
return False
|
|
643
|
+
row_count = _int_payload_value(worksheet, ("row_count", "rows", "total_rows", "max_row"))
|
|
644
|
+
column_count = _int_payload_value(
|
|
645
|
+
worksheet,
|
|
646
|
+
("column_count", "col_count", "columns", "total_columns", "max_column", "max_col"),
|
|
647
|
+
)
|
|
648
|
+
return not ((row_count is not None and row_count <= 0) or (column_count is not None and column_count <= 0))
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _select_beautify_worksheets(target, worksheet_response: dict[str, object]) -> list[dict[str, object]]:
|
|
652
|
+
styleable = [worksheet for worksheet in _worksheet_items(worksheet_response) if _is_styleable_worksheet(worksheet)]
|
|
653
|
+
if not styleable:
|
|
654
|
+
if target.worksheet_name:
|
|
655
|
+
raise UsageError(f"No styleable worksheet found for --worksheet-name {target.worksheet_name!r}.")
|
|
656
|
+
raise UsageError("No styleable worksheets found. The workbook may be empty or missing dimensions.")
|
|
657
|
+
if target.worksheet_name:
|
|
658
|
+
selected = [worksheet for worksheet in styleable if _worksheet_name(worksheet) == target.worksheet_name]
|
|
659
|
+
if not selected:
|
|
660
|
+
raise UsageError(f"No styleable worksheet found for --worksheet-name {target.worksheet_name!r}.")
|
|
661
|
+
return selected[:1]
|
|
662
|
+
if target.gid is not None:
|
|
663
|
+
selected = [worksheet for worksheet in styleable if _worksheet_gid(worksheet) == target.gid]
|
|
664
|
+
if not selected:
|
|
665
|
+
raise UsageError(f"No styleable worksheet found for gid {target.gid}.")
|
|
666
|
+
return selected[:1]
|
|
667
|
+
if len(styleable) == 1:
|
|
668
|
+
return styleable
|
|
669
|
+
raise UsageError("Workbook has multiple styleable worksheets. Provide --gid or --worksheet-name.")
|
|
670
|
+
|
|
671
|
+
|
|
583
672
|
def _bounded_table_range_from_shape(column_count: int, row_count: int) -> str:
|
|
584
673
|
if column_count <= 0 or row_count <= 0:
|
|
585
674
|
raise UsageError("Cannot beautify an empty worksheet.")
|
|
@@ -867,6 +956,7 @@ def _excel_column_style_operations(
|
|
|
867
956
|
continue
|
|
868
957
|
operations.append(
|
|
869
958
|
{
|
|
959
|
+
"kind": "semantic_column_style",
|
|
870
960
|
"range_addresses": [
|
|
871
961
|
f"{_column_letters_from_index(run_start)}2:{_column_letters_from_index(one_based_index - 1)}{data_last_row}"
|
|
872
962
|
],
|
|
@@ -877,6 +967,7 @@ def _excel_column_style_operations(
|
|
|
877
967
|
run_style = style
|
|
878
968
|
operations.append(
|
|
879
969
|
{
|
|
970
|
+
"kind": "semantic_column_style",
|
|
880
971
|
"range_addresses": [
|
|
881
972
|
f"{_column_letters_from_index(run_start)}2:{_column_letters_from_index(len(headers))}{data_last_row}"
|
|
882
973
|
],
|
|
@@ -886,7 +977,51 @@ def _excel_column_style_operations(
|
|
|
886
977
|
return operations
|
|
887
978
|
|
|
888
979
|
|
|
889
|
-
def
|
|
980
|
+
def _excel_body_banding_operations(
|
|
981
|
+
*,
|
|
982
|
+
column_count: int,
|
|
983
|
+
last_row: int,
|
|
984
|
+
style_policy: dict[str, object],
|
|
985
|
+
) -> list[dict[str, object]]:
|
|
986
|
+
banding = style_policy.get("bodyBanding")
|
|
987
|
+
if not isinstance(banding, dict) or banding.get("enabled") is False:
|
|
988
|
+
return []
|
|
989
|
+
if column_count <= 0 or last_row < 2:
|
|
990
|
+
return []
|
|
991
|
+
odd_background = str(banding.get("oddRowBackground", "#FFFFFF"))
|
|
992
|
+
even_background = str(banding.get("evenRowBackground", "#F8FAFC"))
|
|
993
|
+
last_column = _column_letters_from_index(column_count)
|
|
994
|
+
odd_ranges: list[str] = []
|
|
995
|
+
even_ranges: list[str] = []
|
|
996
|
+
for row_number in range(2, last_row + 1):
|
|
997
|
+
target = odd_ranges if (row_number - 2) % 2 == 0 else even_ranges
|
|
998
|
+
target.append(f"A{row_number}:{last_column}{row_number}")
|
|
999
|
+
operations: list[dict[str, object]] = []
|
|
1000
|
+
if odd_ranges:
|
|
1001
|
+
operations.append(
|
|
1002
|
+
{
|
|
1003
|
+
"kind": "body_banding",
|
|
1004
|
+
"range_addresses": odd_ranges,
|
|
1005
|
+
"style": {"bg_color": odd_background},
|
|
1006
|
+
}
|
|
1007
|
+
)
|
|
1008
|
+
if even_ranges:
|
|
1009
|
+
operations.append(
|
|
1010
|
+
{
|
|
1011
|
+
"kind": "body_banding",
|
|
1012
|
+
"range_addresses": even_ranges,
|
|
1013
|
+
"style": {"bg_color": even_background},
|
|
1014
|
+
}
|
|
1015
|
+
)
|
|
1016
|
+
return operations
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def _excel_beautify_plan(
|
|
1020
|
+
worksheet: dict[str, object],
|
|
1021
|
+
read_response: dict[str, object] | None = None,
|
|
1022
|
+
*,
|
|
1023
|
+
style_policy: dict[str, object] | None = None,
|
|
1024
|
+
) -> dict[str, object]:
|
|
890
1025
|
headers = _header_values_from_read_response(read_response or {})
|
|
891
1026
|
if not headers:
|
|
892
1027
|
column_count = _int_payload_value(
|
|
@@ -902,10 +1037,12 @@ def _excel_beautify_plan(worksheet: dict[str, object], read_response: dict[str,
|
|
|
902
1037
|
classifications = _excel_column_classifications(headers, read_response)
|
|
903
1038
|
last_row = _range_data_last_row(table_range)
|
|
904
1039
|
header_range = f"A1:{_column_letters_from_index(column_count)}1"
|
|
1040
|
+
resolved_style_policy = style_policy or _default_beautify_style_policy()
|
|
905
1041
|
return {
|
|
906
1042
|
"worksheet_name": _worksheet_name(worksheet),
|
|
907
1043
|
"engine": "excelize",
|
|
908
1044
|
"table_range": table_range,
|
|
1045
|
+
"style_policy": resolved_style_policy,
|
|
909
1046
|
"headers": headers,
|
|
910
1047
|
"columns": [
|
|
911
1048
|
_column_classification_plan(
|
|
@@ -917,14 +1054,17 @@ def _excel_beautify_plan(worksheet: dict[str, object], read_response: dict[str,
|
|
|
917
1054
|
],
|
|
918
1055
|
"operations": [
|
|
919
1056
|
{
|
|
1057
|
+
"kind": "freeze_header",
|
|
920
1058
|
"endpoint": "/api/v1/excel/freeze_panes",
|
|
921
1059
|
"payload": {"freeze_rows": 1, "freeze_columns": 0},
|
|
922
1060
|
},
|
|
923
1061
|
{
|
|
1062
|
+
"kind": "filter",
|
|
924
1063
|
"endpoint": "/api/v1/excel/set_auto_filter",
|
|
925
1064
|
"payload": {"auto_filter": {"ref": table_range}},
|
|
926
1065
|
},
|
|
927
1066
|
{
|
|
1067
|
+
"kind": "header_style",
|
|
928
1068
|
"endpoint": "/api/v1/excel/batch_set_cell_style",
|
|
929
1069
|
"payload": {
|
|
930
1070
|
"range_addresses": [header_range],
|
|
@@ -933,8 +1073,21 @@ def _excel_beautify_plan(worksheet: dict[str, object], read_response: dict[str,
|
|
|
933
1073
|
},
|
|
934
1074
|
*[
|
|
935
1075
|
{
|
|
1076
|
+
"kind": operation.get("kind", "body_banding"),
|
|
1077
|
+
"endpoint": "/api/v1/excel/batch_set_cell_style",
|
|
1078
|
+
"payload": {key: value for key, value in operation.items() if key != "kind"},
|
|
1079
|
+
}
|
|
1080
|
+
for operation in _excel_body_banding_operations(
|
|
1081
|
+
column_count=column_count,
|
|
1082
|
+
last_row=last_row,
|
|
1083
|
+
style_policy=resolved_style_policy,
|
|
1084
|
+
)
|
|
1085
|
+
],
|
|
1086
|
+
*[
|
|
1087
|
+
{
|
|
1088
|
+
"kind": operation.get("kind", "semantic_column_style"),
|
|
936
1089
|
"endpoint": "/api/v1/excel/batch_set_cell_style",
|
|
937
|
-
"payload": operation,
|
|
1090
|
+
"payload": {key: value for key, value in operation.items() if key != "kind"},
|
|
938
1091
|
}
|
|
939
1092
|
for operation in _excel_column_style_operations(headers, classifications, last_row)
|
|
940
1093
|
],
|
|
@@ -942,6 +1095,65 @@ def _excel_beautify_plan(worksheet: dict[str, object], read_response: dict[str,
|
|
|
942
1095
|
}
|
|
943
1096
|
|
|
944
1097
|
|
|
1098
|
+
def _bounded_verify_range(table_range: object) -> str:
|
|
1099
|
+
if not isinstance(table_range, str):
|
|
1100
|
+
return "A1:Z20"
|
|
1101
|
+
end = _range_end(table_range)
|
|
1102
|
+
if end is None:
|
|
1103
|
+
return "A1:Z20"
|
|
1104
|
+
column_count = min(column_index_from_letters(end[0]), column_index_from_letters("Z"))
|
|
1105
|
+
row_count = min(end[1], 20)
|
|
1106
|
+
return f"A1:{_column_letters_from_index(column_count)}{row_count}"
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def _excel_beautify_verify(client: MaybeAIClient, *, target, plan: dict[str, object]) -> dict[str, object]:
|
|
1110
|
+
worksheet_name = plan.get("worksheet_name")
|
|
1111
|
+
try:
|
|
1112
|
+
read_payload = base_payload(
|
|
1113
|
+
SimpleNamespace(uri=target.uri, worksheet_name=worksheet_name if isinstance(worksheet_name, str) else None)
|
|
1114
|
+
) | {
|
|
1115
|
+
"range_address": _bounded_verify_range(plan.get("table_range")),
|
|
1116
|
+
"value_render_option": "FORMATTED_VALUE",
|
|
1117
|
+
}
|
|
1118
|
+
read_response = client.post_json("/api/v1/excel/read_sheet", read_payload)
|
|
1119
|
+
return {
|
|
1120
|
+
"success": bool(read_response.get("success", True)),
|
|
1121
|
+
"scope": "worksheet_sample",
|
|
1122
|
+
"endpoint": "/api/v1/excel/read_sheet",
|
|
1123
|
+
"range_address": read_payload["range_address"],
|
|
1124
|
+
"worksheet_name": worksheet_name,
|
|
1125
|
+
"response": read_response,
|
|
1126
|
+
}
|
|
1127
|
+
except Exception as error: # verify is best-effort after a successful apply
|
|
1128
|
+
return {
|
|
1129
|
+
"success": False,
|
|
1130
|
+
"scope": "worksheet_sample",
|
|
1131
|
+
"endpoint": "/api/v1/excel/read_sheet",
|
|
1132
|
+
"worksheet_name": worksheet_name,
|
|
1133
|
+
"error": str(error),
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def _pg_beautify_verify(client: MaybeAIClient, *, target, gid: int) -> dict[str, object]:
|
|
1138
|
+
try:
|
|
1139
|
+
metadata_response = client.post_json("/api/v1/excel/table/metadata", _pg_table_metadata_payload(target, gid))
|
|
1140
|
+
return {
|
|
1141
|
+
"success": bool(metadata_response.get("success", True)),
|
|
1142
|
+
"scope": "field_metadata",
|
|
1143
|
+
"endpoint": "/api/v1/excel/table/metadata",
|
|
1144
|
+
"gid": gid,
|
|
1145
|
+
"response": metadata_response,
|
|
1146
|
+
}
|
|
1147
|
+
except Exception as error:
|
|
1148
|
+
return {
|
|
1149
|
+
"success": False,
|
|
1150
|
+
"scope": "field_metadata",
|
|
1151
|
+
"endpoint": "/api/v1/excel/table/metadata",
|
|
1152
|
+
"gid": gid,
|
|
1153
|
+
"error": str(error),
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
|
|
945
1157
|
def _pg_fields_from_metadata(metadata_response: dict[str, object], *, gid: int, worksheet_name: str | None) -> list[dict[str, object]]:
|
|
946
1158
|
tables = metadata_response.get("tables")
|
|
947
1159
|
if not isinstance(tables, list):
|
|
@@ -1021,11 +1233,17 @@ def _pg_beautify_field_update(
|
|
|
1021
1233
|
style = classification.style
|
|
1022
1234
|
existing_property = field.get("property")
|
|
1023
1235
|
property_payload: dict[str, object] = dict(existing_property) if isinstance(existing_property, dict) else {}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1236
|
+
formatter = str(style["format_code"]) if "format_code" in style else None
|
|
1237
|
+
font_color = str(style["font_color"]) if "font_color" in style else None
|
|
1238
|
+
beautify_property = (
|
|
1239
|
+
_field_property_payload(
|
|
1240
|
+
formatter=formatter,
|
|
1241
|
+
color=font_color,
|
|
1242
|
+
background_color=None,
|
|
1243
|
+
width=None,
|
|
1244
|
+
)
|
|
1245
|
+
if formatter is not None or font_color is not None
|
|
1246
|
+
else {}
|
|
1029
1247
|
)
|
|
1030
1248
|
beautify_style = beautify_property.pop("style", None)
|
|
1031
1249
|
property_payload.update(beautify_property)
|
|
@@ -1909,20 +2127,27 @@ def beautify(
|
|
|
1909
2127
|
doc_id: Optional[str] = typer.Option(None, "--doc-id", "--docs-id", help="MaybeAI document ID."),
|
|
1910
2128
|
url: Optional[str] = typer.Option(None, "--url", help="MaybeAI workbook URL."),
|
|
1911
2129
|
uri: Optional[str] = typer.Option(None, "--uri", help="Resolved workbook URI."),
|
|
2130
|
+
gid: Optional[int] = typer.Option(None, "--gid", help="Worksheet gid."),
|
|
1912
2131
|
worksheet_name: Optional[str] = typer.Option(None, "--worksheet-name", help="Optional worksheet name to beautify."),
|
|
1913
2132
|
mode: Optional[str] = typer.Option(None, "--mode", help="single_table, multi_table, or auto_detect."),
|
|
1914
2133
|
dry_run: bool = typer.Option(False, "--dry-run", help="Preview the generated beautify plan without applying styles."),
|
|
2134
|
+
verify: bool = typer.Option(False, "--verify", help="Read back a bounded sample after applying styles."),
|
|
2135
|
+
config: Optional[Path] = typer.Option(None, "--config", exists=True, dir_okay=False, help="Optional JSON beautify style policy."),
|
|
1915
2136
|
output: Optional[str] = output_option(),
|
|
1916
2137
|
verbose: Optional[bool] = verbose_option(),
|
|
1917
2138
|
timeout: Optional[float] = timeout_option(),
|
|
1918
2139
|
) -> None:
|
|
1919
2140
|
try:
|
|
2141
|
+
if dry_run and verify:
|
|
2142
|
+
raise UsageError("--dry-run and --verify cannot be combined.")
|
|
2143
|
+
config_payload, config_source = _beautify_config(config)
|
|
2144
|
+
style_policy = _beautify_style_policy(config_payload)
|
|
1920
2145
|
state = common_options(
|
|
1921
2146
|
ctx,
|
|
1922
2147
|
doc_id=doc_id,
|
|
1923
2148
|
url=url,
|
|
1924
2149
|
uri=uri,
|
|
1925
|
-
gid=
|
|
2150
|
+
gid=gid,
|
|
1926
2151
|
worksheet_name=worksheet_name,
|
|
1927
2152
|
output=output,
|
|
1928
2153
|
verbose=verbose,
|
|
@@ -1939,28 +2164,11 @@ def beautify(
|
|
|
1939
2164
|
},
|
|
1940
2165
|
)
|
|
1941
2166
|
_worksheet_style_mode(mode)
|
|
1942
|
-
selected_worksheets
|
|
1943
|
-
for worksheet in _worksheet_items(worksheet_response):
|
|
1944
|
-
worksheet_item_name = _worksheet_name(worksheet)
|
|
1945
|
-
if not worksheet_item_name:
|
|
1946
|
-
continue
|
|
1947
|
-
if worksheet_name and worksheet_item_name != worksheet_name:
|
|
1948
|
-
continue
|
|
1949
|
-
row_count = _int_payload_value(worksheet, ("row_count", "rows", "total_rows", "max_row"))
|
|
1950
|
-
column_count = _int_payload_value(
|
|
1951
|
-
worksheet,
|
|
1952
|
-
("column_count", "col_count", "columns", "total_columns", "max_column", "max_col"),
|
|
1953
|
-
)
|
|
1954
|
-
if (row_count is not None and row_count <= 0) or (column_count is not None and column_count <= 0):
|
|
1955
|
-
continue
|
|
1956
|
-
selected_worksheets.append(worksheet)
|
|
1957
|
-
if not selected_worksheets:
|
|
1958
|
-
if worksheet_name:
|
|
1959
|
-
raise UsageError(f"No styleable worksheet found for --worksheet-name {worksheet_name!r}.")
|
|
1960
|
-
raise UsageError("No styleable worksheets found. The workbook may be empty or missing dimensions.")
|
|
2167
|
+
selected_worksheets = _select_beautify_worksheets(target, worksheet_response)
|
|
1961
2168
|
|
|
1962
2169
|
plans: list[dict[str, object]] = []
|
|
1963
2170
|
responses: list[dict[str, object]] = []
|
|
2171
|
+
verify_results: list[dict[str, object]] = []
|
|
1964
2172
|
if dry_run:
|
|
1965
2173
|
for worksheet in selected_worksheets:
|
|
1966
2174
|
engine = _normalize_engine(worksheet.get("data_engine") or worksheet.get("engine"))
|
|
@@ -1993,6 +2201,7 @@ def beautify(
|
|
|
1993
2201
|
"gid": gid,
|
|
1994
2202
|
"metadata_endpoint": "/api/v1/excel/table/metadata",
|
|
1995
2203
|
"operation": "field formatter/style updates with header layout defaults",
|
|
2204
|
+
"style_policy": style_policy,
|
|
1996
2205
|
"layout": _pg_beautify_layout_plan(len(fields)),
|
|
1997
2206
|
"columns": columns,
|
|
1998
2207
|
"fields": columns,
|
|
@@ -2007,7 +2216,7 @@ def beautify(
|
|
|
2007
2216
|
"value_render_option": "FORMATTED_VALUE",
|
|
2008
2217
|
}
|
|
2009
2218
|
read_response = client.post_json("/api/v1/excel/read_sheet", read_payload)
|
|
2010
|
-
plans.append(_excel_beautify_plan(worksheet, read_response))
|
|
2219
|
+
plans.append(_excel_beautify_plan(worksheet, read_response, style_policy=style_policy))
|
|
2011
2220
|
response: dict[str, object] = {
|
|
2012
2221
|
"success": True,
|
|
2013
2222
|
"dry_run": True,
|
|
@@ -2076,6 +2285,8 @@ def beautify(
|
|
|
2076
2285
|
)
|
|
2077
2286
|
if fallback_reason:
|
|
2078
2287
|
responses[-1]["batch_update_fallback_reason"] = fallback_reason
|
|
2288
|
+
if verify:
|
|
2289
|
+
verify_results.append(_pg_beautify_verify(client, target=target, gid=gid))
|
|
2079
2290
|
continue
|
|
2080
2291
|
|
|
2081
2292
|
read_payload = base_payload(
|
|
@@ -2085,7 +2296,7 @@ def beautify(
|
|
|
2085
2296
|
"value_render_option": "FORMATTED_VALUE",
|
|
2086
2297
|
}
|
|
2087
2298
|
read_response = client.post_json("/api/v1/excel/read_sheet", read_payload)
|
|
2088
|
-
plan = _excel_beautify_plan(worksheet, read_response)
|
|
2299
|
+
plan = _excel_beautify_plan(worksheet, read_response, style_policy=style_policy)
|
|
2089
2300
|
plans.append(plan)
|
|
2090
2301
|
operation_responses: list[dict[str, object]] = []
|
|
2091
2302
|
for operation in plan["operations"]:
|
|
@@ -2109,6 +2320,8 @@ def beautify(
|
|
|
2109
2320
|
"operations": operation_responses,
|
|
2110
2321
|
}
|
|
2111
2322
|
)
|
|
2323
|
+
if verify:
|
|
2324
|
+
verify_results.append(_excel_beautify_verify(client, target=target, plan=plan))
|
|
2112
2325
|
response = {
|
|
2113
2326
|
"success": all(bool(item.get("success", True)) for item in responses),
|
|
2114
2327
|
"styled_worksheet_count": len(responses),
|
|
@@ -2142,7 +2355,10 @@ def beautify(
|
|
|
2142
2355
|
"list_endpoint": list_endpoint,
|
|
2143
2356
|
"worksheet_count": len(plans),
|
|
2144
2357
|
"engine": plans[0].get("engine") if len(plans) == 1 else "mixed",
|
|
2358
|
+
"config_applied": config_payload is not None,
|
|
2359
|
+
**({"config_source": config_source} if config_source else {}),
|
|
2145
2360
|
},
|
|
2361
|
+
verify=verify_results[0] if len(verify_results) == 1 else (verify_results or None),
|
|
2146
2362
|
metadata=get_endpoint_metadata("style.beautify") if state.verbose else None,
|
|
2147
2363
|
),
|
|
2148
2364
|
state.output,
|