@qingflow-tech/qingflow-app-builder-mcp 1.0.7 → 1.0.8

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.
@@ -9,6 +9,22 @@ from .button_style_catalog import resolve_button_style
9
9
  from ..solution.spec_models import StrictModel
10
10
 
11
11
 
12
+ def _normalize_builder_view_key_value(value: Any) -> Any:
13
+ if not isinstance(value, str):
14
+ return value
15
+ raw = value.strip()
16
+ if raw.startswith("custom:"):
17
+ return raw.split(":", 1)[1].strip()
18
+ return raw
19
+
20
+
21
+ def _normalize_builder_view_key_payload(payload: dict[str, Any]) -> dict[str, Any]:
22
+ for key in ("view_key", "viewKey", "viewgraphKey", "viewGraphKey"):
23
+ if key in payload:
24
+ payload[key] = _normalize_builder_view_key_value(payload[key])
25
+ return payload
26
+
27
+
12
28
  class PublicFieldType(str, Enum):
13
29
  text = "text"
14
30
  long_text = "long_text"
@@ -115,7 +131,7 @@ class PublicViewButtonType(str, Enum):
115
131
  class PublicViewButtonConfigType(str, Enum):
116
132
  top = "TOP"
117
133
  detail = "DETAIL"
118
- list = "LIST"
134
+ list = "INSIDE"
119
135
 
120
136
 
121
137
  class PublicButtonPlacement(str, Enum):
@@ -1068,6 +1084,11 @@ class ViewUpsertPatch(StrictModel):
1068
1084
  "asosChartConfig",
1069
1085
  ),
1070
1086
  )
1087
+ partial_update: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_partial_update", "partial_update", "partialUpdate"))
1088
+ preserve_filters: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_filters", "preserve_filters", "preserveFilters"))
1089
+ preserve_buttons: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_buttons", "preserve_buttons", "preserveButtons"))
1090
+ preserve_query_conditions: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_query_conditions", "preserve_query_conditions", "preserveQueryConditions"))
1091
+ preserve_associated_resources: bool = Field(default=False, exclude=True, validation_alias=AliasChoices("_preserve_associated_resources", "preserve_associated_resources", "preserveAssociatedResources"))
1071
1092
 
1072
1093
  @model_validator(mode="before")
1073
1094
  @classmethod
@@ -1075,6 +1096,7 @@ class ViewUpsertPatch(StrictModel):
1075
1096
  if not isinstance(value, dict):
1076
1097
  return value
1077
1098
  payload = dict(value)
1099
+ payload = _normalize_builder_view_key_payload(payload)
1078
1100
  if "fields" in payload and "columns" not in payload:
1079
1101
  payload["columns"] = payload.pop("fields")
1080
1102
  if "column_names" in payload and "columns" not in payload:
@@ -1123,6 +1145,28 @@ class ViewUpsertPatch(StrictModel):
1123
1145
  return self
1124
1146
 
1125
1147
 
1148
+ class ViewPartialPatch(StrictModel):
1149
+ view_key: str | None = Field(default=None, validation_alias=AliasChoices("view_key", "viewKey", "viewgraphKey", "viewGraphKey"))
1150
+ name: str | None = Field(default=None, validation_alias=AliasChoices("name", "view_name", "viewName"))
1151
+ set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
1152
+ unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
1153
+
1154
+ @model_validator(mode="before")
1155
+ @classmethod
1156
+ def normalize_aliases(cls, value: Any) -> Any:
1157
+ if not isinstance(value, dict):
1158
+ return value
1159
+ return _normalize_builder_view_key_payload(dict(value))
1160
+
1161
+ @model_validator(mode="after")
1162
+ def validate_shape(self) -> "ViewPartialPatch":
1163
+ if not str(self.view_key or "").strip() and not str(self.name or "").strip():
1164
+ raise ValueError("patch_views[] requires view_key or unique name")
1165
+ if not self.set and not self.unset:
1166
+ raise ValueError("patch_views[] requires set or unset")
1167
+ return self
1168
+
1169
+
1126
1170
  class CustomButtonJudgeValuePatch(StrictModel):
1127
1171
  id: int | str | None = None
1128
1172
  value: Any | None = None
@@ -1190,6 +1234,21 @@ class CustomButtonFieldMappingPatch(StrictModel):
1190
1234
  target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target"))
1191
1235
 
1192
1236
 
1237
+ class FieldMatchMappingPatch(StrictModel):
1238
+ target_field: Any = Field(validation_alias=AliasChoices("target_field", "targetField", "target", "field"))
1239
+ source_field: Any | None = Field(default=None, validation_alias=AliasChoices("source_field", "sourceField", "source"))
1240
+ value: Any | None = Field(default=None, validation_alias=AliasChoices("value", "static_value", "staticValue"))
1241
+ operator: str = Field(default="eq", validation_alias=AliasChoices("operator", "op", "judge_type", "judgeType"))
1242
+
1243
+ @model_validator(mode="after")
1244
+ def validate_shape(self) -> "FieldMatchMappingPatch":
1245
+ has_source = self.source_field is not None and str(self.source_field).strip() != ""
1246
+ has_value = self.value is not None
1247
+ if has_source == has_value:
1248
+ raise ValueError("match_mappings[] requires exactly one of source_field or value")
1249
+ return self
1250
+
1251
+
1193
1252
  class CustomButtonAddDataConfigPatch(StrictModel):
1194
1253
  related_app_key: str | None = Field(
1195
1254
  default=None,
@@ -1319,6 +1378,22 @@ class CustomButtonUpsertPatch(CustomButtonPatch):
1319
1378
  client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
1320
1379
 
1321
1380
 
1381
+ class CustomButtonPartialPatch(StrictModel):
1382
+ button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
1383
+ button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText", "name"))
1384
+ client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
1385
+ set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
1386
+ unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
1387
+
1388
+ @model_validator(mode="after")
1389
+ def validate_shape(self) -> "CustomButtonPartialPatch":
1390
+ if self.button_id is None and not str(self.button_text or "").strip():
1391
+ raise ValueError("patch_buttons[] requires button_id or unique button_text")
1392
+ if not self.set and not self.unset:
1393
+ raise ValueError("patch_buttons[] requires set or unset")
1394
+ return self
1395
+
1396
+
1322
1397
  class CustomButtonRemovePatch(StrictModel):
1323
1398
  button_id: int | None = Field(default=None, validation_alias=AliasChoices("button_id", "buttonId", "id"))
1324
1399
  button_text: str | None = Field(default=None, validation_alias=AliasChoices("button_text", "buttonText", "name"))
@@ -1368,6 +1443,13 @@ class CustomButtonViewConfigPatch(StrictModel):
1368
1443
  mode: str = Field(default="merge", validation_alias=AliasChoices("mode", "apply_mode", "applyMode"))
1369
1444
  buttons: list[CustomButtonViewButtonBindingPatch] = Field(default_factory=list)
1370
1445
 
1446
+ @model_validator(mode="before")
1447
+ @classmethod
1448
+ def normalize_aliases(cls, value: Any) -> Any:
1449
+ if not isinstance(value, dict):
1450
+ return value
1451
+ return _normalize_builder_view_key_payload(dict(value))
1452
+
1371
1453
  @model_validator(mode="after")
1372
1454
  def validate_mode(self) -> "CustomButtonViewConfigPatch":
1373
1455
  normalized = str(self.mode or "").strip().lower()
@@ -1382,6 +1464,7 @@ class CustomButtonViewConfigPatch(StrictModel):
1382
1464
  class CustomButtonsApplyRequest(StrictModel):
1383
1465
  app_key: str
1384
1466
  upsert_buttons: list[CustomButtonUpsertPatch] = Field(default_factory=list)
1467
+ patch_buttons: list[CustomButtonPartialPatch] = Field(default_factory=list)
1385
1468
  remove_buttons: list[CustomButtonRemovePatch] = Field(default_factory=list)
1386
1469
  view_configs: list[CustomButtonViewConfigPatch] = Field(default_factory=list)
1387
1470
 
@@ -1395,6 +1478,8 @@ class CustomButtonsApplyRequest(StrictModel):
1395
1478
  payload["upsert_buttons"] = payload.pop("buttons")
1396
1479
  if "upsertButtons" in payload and "upsert_buttons" not in payload:
1397
1480
  payload["upsert_buttons"] = payload.pop("upsertButtons")
1481
+ if "patchButtons" in payload and "patch_buttons" not in payload:
1482
+ payload["patch_buttons"] = payload.pop("patchButtons")
1398
1483
  if "removeButtons" in payload and "remove_buttons" not in payload:
1399
1484
  payload["remove_buttons"] = payload.pop("removeButtons")
1400
1485
  if "viewConfigs" in payload and "view_configs" not in payload:
@@ -1403,8 +1488,8 @@ class CustomButtonsApplyRequest(StrictModel):
1403
1488
 
1404
1489
  @model_validator(mode="after")
1405
1490
  def validate_shape(self) -> "CustomButtonsApplyRequest":
1406
- if not self.upsert_buttons and not self.remove_buttons and not self.view_configs:
1407
- raise ValueError("custom button apply requires at least one upsert, remove, or view config operation")
1491
+ if not self.upsert_buttons and not self.patch_buttons and not self.remove_buttons and not self.view_configs:
1492
+ raise ValueError("custom button apply requires at least one upsert, patch, remove, or view config operation")
1408
1493
  return self
1409
1494
 
1410
1495
 
@@ -1421,6 +1506,10 @@ class AssociatedResourceUpsertPatch(StrictModel):
1421
1506
  report_source: str | None = Field(default=None, validation_alias=AliasChoices("report_source", "reportSource"))
1422
1507
  source_type: str | None = Field(default=None, validation_alias=AliasChoices("source_type", "sourceType"))
1423
1508
  match_rules: list[CustomButtonMatchRulePatch] = Field(default_factory=list, validation_alias=AliasChoices("match_rules", "matchRules"))
1509
+ match_mappings: list[FieldMatchMappingPatch] = Field(
1510
+ default_factory=list,
1511
+ validation_alias=AliasChoices("match_mappings", "matchMappings"),
1512
+ )
1424
1513
 
1425
1514
  @model_validator(mode="before")
1426
1515
  @classmethod
@@ -1428,6 +1517,7 @@ class AssociatedResourceUpsertPatch(StrictModel):
1428
1517
  if not isinstance(value, dict):
1429
1518
  return value
1430
1519
  payload = dict(value)
1520
+ payload = _normalize_builder_view_key_payload(payload)
1431
1521
  if "chart_id" in payload and "chart_key" not in payload and "chartKey" not in payload:
1432
1522
  payload["chart_key"] = str(payload.pop("chart_id"))
1433
1523
  raw_graph_type = str(payload.get("graph_type", payload.get("graphType", "")) or "").strip().lower()
@@ -1449,6 +1539,21 @@ class AssociatedResourceUpsertPatch(StrictModel):
1449
1539
  return payload
1450
1540
 
1451
1541
 
1542
+ class AssociatedResourcePartialPatch(StrictModel):
1543
+ associated_item_id: int = Field(validation_alias=AliasChoices("associated_item_id", "associatedItemId", "asosChartId", "id"))
1544
+ client_key: str | None = Field(default=None, validation_alias=AliasChoices("client_key", "clientKey"))
1545
+ set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
1546
+ unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
1547
+
1548
+ @model_validator(mode="after")
1549
+ def validate_shape(self) -> "AssociatedResourcePartialPatch":
1550
+ if self.associated_item_id <= 0:
1551
+ raise ValueError("patch_resources[].associated_item_id must be a positive integer")
1552
+ if not self.set and not self.unset:
1553
+ raise ValueError("patch_resources[] requires set or unset")
1554
+ return self
1555
+
1556
+
1452
1557
  class AssociatedResourceViewConfigPatch(StrictModel):
1453
1558
  view_key: str = Field(validation_alias=AliasChoices("view_key", "viewKey", "viewgraphKey", "viewGraphKey"))
1454
1559
  visible: bool = Field(default=True, validation_alias=AliasChoices("visible", "enabled", "asosChartVisible"))
@@ -1459,10 +1564,18 @@ class AssociatedResourceViewConfigPatch(StrictModel):
1459
1564
  )
1460
1565
  associated_item_refs: list[str] = Field(default_factory=list, validation_alias=AliasChoices("associated_item_refs", "associatedItemRefs", "refs"))
1461
1566
 
1567
+ @model_validator(mode="before")
1568
+ @classmethod
1569
+ def normalize_aliases(cls, value: Any) -> Any:
1570
+ if not isinstance(value, dict):
1571
+ return value
1572
+ return _normalize_builder_view_key_payload(dict(value))
1573
+
1462
1574
 
1463
1575
  class AssociatedResourcesApplyRequest(StrictModel):
1464
1576
  app_key: str
1465
1577
  upsert_resources: list[AssociatedResourceUpsertPatch] = Field(default_factory=list)
1578
+ patch_resources: list[AssociatedResourcePartialPatch] = Field(default_factory=list)
1466
1579
  remove_associated_item_ids: list[int] = Field(default_factory=list)
1467
1580
  reorder_associated_item_ids: list[int] = Field(default_factory=list)
1468
1581
  view_configs: list[AssociatedResourceViewConfigPatch] = Field(default_factory=list)
@@ -1475,6 +1588,8 @@ class AssociatedResourcesApplyRequest(StrictModel):
1475
1588
  payload = dict(value)
1476
1589
  if "upsertResources" in payload and "upsert_resources" not in payload:
1477
1590
  payload["upsert_resources"] = payload.pop("upsertResources")
1591
+ if "patchResources" in payload and "patch_resources" not in payload:
1592
+ payload["patch_resources"] = payload.pop("patchResources")
1478
1593
  if "resources" in payload and "upsert_resources" not in payload:
1479
1594
  payload["upsert_resources"] = payload.pop("resources")
1480
1595
  if "removeAssociatedItemIds" in payload and "remove_associated_item_ids" not in payload:
@@ -1487,8 +1602,8 @@ class AssociatedResourcesApplyRequest(StrictModel):
1487
1602
 
1488
1603
  @model_validator(mode="after")
1489
1604
  def validate_shape(self) -> "AssociatedResourcesApplyRequest":
1490
- if not self.upsert_resources and not self.remove_associated_item_ids and not self.reorder_associated_item_ids and not self.view_configs:
1491
- raise ValueError("associated resources apply requires at least one resource or view config operation")
1605
+ if not self.upsert_resources and not self.patch_resources and not self.remove_associated_item_ids and not self.reorder_associated_item_ids and not self.view_configs:
1606
+ raise ValueError("associated resources apply requires at least one upsert, patch, remove, reorder, or view config operation")
1492
1607
  return self
1493
1608
 
1494
1609
 
@@ -1533,8 +1648,8 @@ class ViewButtonBindingPatch(StrictModel):
1533
1648
  payload["config_type"] = "TOP"
1534
1649
  elif normalized_config == "detail":
1535
1650
  payload["config_type"] = "DETAIL"
1536
- elif normalized_config in {"list", "row", "row_action"}:
1537
- payload["config_type"] = "LIST"
1651
+ elif normalized_config in {"inside", "list", "row", "row_action"}:
1652
+ payload["config_type"] = "INSIDE"
1538
1653
  raw_limits = payload.get("button_limit", payload.get("buttonLimit"))
1539
1654
  if isinstance(raw_limits, list) and raw_limits and all(isinstance(item, dict) for item in raw_limits):
1540
1655
  payload["button_limit"] = [raw_limits]
@@ -1676,9 +1791,39 @@ class ChartUpsertPatch(StrictModel):
1676
1791
  return payload
1677
1792
 
1678
1793
 
1794
+ class ChartPartialPatch(StrictModel):
1795
+ chart_id: str | None = None
1796
+ name: str | None = None
1797
+ set: dict[str, Any] = Field(default_factory=dict, validation_alias=AliasChoices("set", "update", "values"))
1798
+ unset: list[str] = Field(default_factory=list, validation_alias=AliasChoices("unset", "clear", "remove"))
1799
+
1800
+ @model_validator(mode="before")
1801
+ @classmethod
1802
+ def normalize_aliases(cls, value: Any) -> Any:
1803
+ if not isinstance(value, dict):
1804
+ return value
1805
+ payload = dict(value)
1806
+ if "id" in payload and "chart_id" not in payload:
1807
+ payload["chart_id"] = str(payload.pop("id"))
1808
+ if "chart_name" in payload and "name" not in payload:
1809
+ payload["name"] = payload.pop("chart_name")
1810
+ if "chartName" in payload and "name" not in payload:
1811
+ payload["name"] = payload.pop("chartName")
1812
+ return payload
1813
+
1814
+ @model_validator(mode="after")
1815
+ def validate_shape(self) -> "ChartPartialPatch":
1816
+ if not str(self.chart_id or "").strip() and not str(self.name or "").strip():
1817
+ raise ValueError("patch_charts[] requires chart_id or unique name")
1818
+ if not self.set and not self.unset:
1819
+ raise ValueError("patch_charts[] requires set or unset")
1820
+ return self
1821
+
1822
+
1679
1823
  class ChartApplyRequest(StrictModel):
1680
1824
  app_key: str
1681
1825
  upsert_charts: list[ChartUpsertPatch] = Field(default_factory=list)
1826
+ patch_charts: list[ChartPartialPatch] = Field(default_factory=list)
1682
1827
  remove_chart_ids: list[str] = Field(default_factory=list)
1683
1828
  reorder_chart_ids: list[str] = Field(default_factory=list)
1684
1829
 
@@ -1688,6 +1833,14 @@ class ChartApplyRequest(StrictModel):
1688
1833
  if not isinstance(value, dict):
1689
1834
  return value
1690
1835
  payload = dict(value)
1836
+ if "upsertCharts" in payload and "upsert_charts" not in payload:
1837
+ payload["upsert_charts"] = payload.pop("upsertCharts")
1838
+ if "patchCharts" in payload and "patch_charts" not in payload:
1839
+ payload["patch_charts"] = payload.pop("patchCharts")
1840
+ if "removeChartIds" in payload and "remove_chart_ids" not in payload:
1841
+ payload["remove_chart_ids"] = payload.pop("removeChartIds")
1842
+ if "reorderChartIds" in payload and "reorder_chart_ids" not in payload:
1843
+ payload["reorder_chart_ids"] = payload.pop("reorderChartIds")
1691
1844
  for key in ("remove_chart_ids", "reorder_chart_ids"):
1692
1845
  raw = payload.get(key)
1693
1846
  if isinstance(raw, list):
@@ -1696,8 +1849,8 @@ class ChartApplyRequest(StrictModel):
1696
1849
 
1697
1850
  @model_validator(mode="after")
1698
1851
  def validate_shape(self) -> "ChartApplyRequest":
1699
- if not self.upsert_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
1700
- raise ValueError("chart apply requires at least one upsert, remove, or reorder operation")
1852
+ if not self.upsert_charts and not self.patch_charts and not self.remove_chart_ids and not self.reorder_chart_ids:
1853
+ raise ValueError("chart apply requires at least one upsert, patch, remove, or reorder operation")
1701
1854
  return self
1702
1855
 
1703
1856
 
@@ -1757,6 +1910,13 @@ class PortalViewRefPatch(StrictModel):
1757
1910
  view_key: str | None = None
1758
1911
  view_name: str | None = None
1759
1912
 
1913
+ @model_validator(mode="before")
1914
+ @classmethod
1915
+ def normalize_aliases(cls, value: Any) -> Any:
1916
+ if not isinstance(value, dict):
1917
+ return value
1918
+ return _normalize_builder_view_key_payload(dict(value))
1919
+
1760
1920
  @model_validator(mode="after")
1761
1921
  def validate_target(self) -> "PortalViewRefPatch":
1762
1922
  if not (self.view_key or self.view_name):
@@ -1944,6 +2104,7 @@ class ViewGetResponse(StrictModel):
1944
2104
  config: dict[str, Any] = Field(default_factory=dict)
1945
2105
  questions: list[dict[str, Any]] = Field(default_factory=list)
1946
2106
  associations: list[dict[str, Any]] = Field(default_factory=list)
2107
+ buttons_config: dict[str, Any] = Field(default_factory=dict)
1947
2108
  associated_resources_config: dict[str, Any] = Field(default_factory=dict)
1948
2109
 
1949
2110
 
@@ -2021,6 +2182,7 @@ class FlowPlanRequest(StrictModel):
2021
2182
  class ViewsPlanRequest(StrictModel):
2022
2183
  app_key: str
2023
2184
  upsert_views: list[ViewUpsertPatch] = Field(default_factory=list)
2185
+ patch_views: list[ViewPartialPatch] = Field(default_factory=list)
2024
2186
  remove_views: list[str] = Field(default_factory=list)
2025
2187
  preset: ViewsPreset | None = None
2026
2188