dwf-platform-cli 0.2.0__py3-none-any.whl

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.
Files changed (47) hide show
  1. dwf_cli/__init__.py +3 -0
  2. dwf_cli/__main__.py +4 -0
  3. dwf_cli/api/__init__.py +3 -0
  4. dwf_cli/api/auth.py +46 -0
  5. dwf_cli/api/client.py +113 -0
  6. dwf_cli/api/datamodel.py +458 -0
  7. dwf_cli/api/formmodel.py +197 -0
  8. dwf_cli/api/funcmodel.py +436 -0
  9. dwf_cli/cli/__init__.py +69 -0
  10. dwf_cli/cli/_common.py +152 -0
  11. dwf_cli/cli/auth.py +197 -0
  12. dwf_cli/cli/config.py +200 -0
  13. dwf_cli/cli/datamodel.py +2270 -0
  14. dwf_cli/cli/formmodel.py +1007 -0
  15. dwf_cli/cli/funcmodel.py +2055 -0
  16. dwf_cli/cli/schema.py +210 -0
  17. dwf_cli/core/__init__.py +0 -0
  18. dwf_cli/core/config.py +177 -0
  19. dwf_cli/core/crypto.py +22 -0
  20. dwf_cli/core/errors.py +63 -0
  21. dwf_cli/core/output.py +6 -0
  22. dwf_cli/core/validator.py +129 -0
  23. dwf_cli/mcp/__init__.py +3 -0
  24. dwf_cli/mcp/server.py +411 -0
  25. dwf_cli/schemas/__init__.py +37 -0
  26. dwf_cli/schemas/datamodel/attribute_bind.schema.json +21 -0
  27. dwf_cli/schemas/datamodel/attribute_create.schema.json +24 -0
  28. dwf_cli/schemas/datamodel/attribute_update.schema.json +21 -0
  29. dwf_cli/schemas/datamodel/create.schema.json +27 -0
  30. dwf_cli/schemas/datamodel/excel_confirm.schema.json +65 -0
  31. dwf_cli/schemas/datamodel/external_create.schema.json +47 -0
  32. dwf_cli/schemas/datamodel/external_update.schema.json +47 -0
  33. dwf_cli/schemas/datamodel/object_create.schema.json +24 -0
  34. dwf_cli/schemas/datamodel/object_update.schema.json +17 -0
  35. dwf_cli/schemas/datamodel/relation_create.schema.json +34 -0
  36. dwf_cli/schemas/datamodel/relation_update.schema.json +34 -0
  37. dwf_cli/schemas/datamodel/update.schema.json +26 -0
  38. dwf_cli/schemas/funcmodel/app_create.schema.json +150 -0
  39. dwf_cli/schemas/funcmodel/app_update.schema.json +153 -0
  40. dwf_cli/schemas/funcmodel/language-package_create.schema.json +15 -0
  41. dwf_cli/schemas/funcmodel/operations_create.schema.json +77 -0
  42. dwf_cli/schemas/funcmodel/operations_update.schema.json +76 -0
  43. dwf_platform_cli-0.2.0.dist-info/METADATA +347 -0
  44. dwf_platform_cli-0.2.0.dist-info/RECORD +47 -0
  45. dwf_platform_cli-0.2.0.dist-info/WHEEL +4 -0
  46. dwf_platform_cli-0.2.0.dist-info/entry_points.txt +3 -0
  47. dwf_platform_cli-0.2.0.dist-info/licenses/LICENSE +190 -0
dwf_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from dwf_cli.cli import app
2
+
3
+ __all__ = ["app"]
dwf_cli/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from dwf_cli.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app(prog_name="dwf-cli")
@@ -0,0 +1,3 @@
1
+ from dwf_cli.api.client import APIClient
2
+
3
+ __all__ = ["APIClient"]
dwf_cli/api/auth.py ADDED
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import httpx
6
+
7
+ from dwf_cli.core.crypto import encrypt_password
8
+ from dwf_cli.core.errors import AuthError
9
+
10
+ if TYPE_CHECKING:
11
+ from dwf_cli.api.client import APIClient
12
+
13
+
14
+ def login(
15
+ client: APIClient,
16
+ username: str,
17
+ password: str,
18
+ aes_key: str,
19
+ aes_iv: str,
20
+ ) -> str:
21
+ encrypted_pwd = encrypt_password(password, aes_key, aes_iv)
22
+ response = client._http.request(
23
+ "POST",
24
+ "/dwf/v1/login",
25
+ auth=httpx.BasicAuth(username, password),
26
+ files={
27
+ "username": (None, username),
28
+ "password": (None, encrypted_pwd),
29
+ },
30
+ )
31
+ if response.status_code != 200:
32
+ raise AuthError(
33
+ f"Login failed (status={response.status_code}, body={response.text[:200]})"
34
+ )
35
+ token = response.headers.get("authorization")
36
+ if not token:
37
+ raise AuthError("No authorization token in login response header")
38
+ return token
39
+
40
+
41
+ def validate_token(client: APIClient) -> bool:
42
+ try:
43
+ client.get("/dwf/v1/org/current-user-environment")
44
+ return True
45
+ except Exception:
46
+ return False
dwf_cli/api/client.py ADDED
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import httpx
6
+
7
+ from dwf_cli.core.errors import APIError, AuthError, ConflictError
8
+
9
+
10
+ def raise_if_not_success(result: object, action: str = "Operation") -> None:
11
+ if isinstance(result, dict) and result.get("success") is False:
12
+ msg = result.get("message", "Unknown error")
13
+ raise APIError(200, f"{action} failed: {msg}")
14
+
15
+
16
+ class APIClient:
17
+ def __init__(
18
+ self,
19
+ base_url: str,
20
+ token: str | None = None,
21
+ timeout: float = 30.0,
22
+ ) -> None:
23
+ self.base_url = base_url.rstrip("/")
24
+ self.token = token
25
+ self._http = httpx.Client(
26
+ base_url=self.base_url,
27
+ timeout=timeout,
28
+ )
29
+
30
+ def _request(
31
+ self,
32
+ method: str,
33
+ path: str,
34
+ *,
35
+ params: dict[str, Any] | None = None,
36
+ json: dict[str, Any] | None = None,
37
+ files: dict[str, Any] | None = None,
38
+ ) -> Any:
39
+ headers: dict[str, str] = {}
40
+ if self.token:
41
+ headers["Authorization"] = self.token
42
+
43
+ response = self._http.request(
44
+ method,
45
+ path,
46
+ headers=headers,
47
+ params=params,
48
+ json=json,
49
+ files=files,
50
+ )
51
+
52
+ if response.status_code == 401:
53
+ raise AuthError(f"Unauthorized (body: {response.text[:200]})")
54
+ if response.status_code == 404:
55
+ from dwf_cli.core.errors import NotFoundError
56
+
57
+ raise NotFoundError(f"Not found: {path}")
58
+ if response.status_code == 409:
59
+ raise ConflictError(
60
+ f"Conflict: {path}",
61
+ detail=response.text[:500],
62
+ )
63
+ if response.status_code >= 400:
64
+ raise APIError(response.status_code, response.text)
65
+
66
+ if response.status_code == 204:
67
+ return None
68
+
69
+ content_type = response.headers.get("content-type", "")
70
+ if "application/json" in content_type:
71
+ return response.json()
72
+ return response
73
+
74
+ def download(self, path: str, **kwargs: Any) -> httpx.Response:
75
+ headers: dict[str, str] = {}
76
+ if self.token:
77
+ headers["Authorization"] = self.token
78
+ response = self._http.request(
79
+ "GET" if "method" not in kwargs else kwargs.pop("method"),
80
+ path,
81
+ headers=headers,
82
+ **kwargs,
83
+ )
84
+ if response.status_code == 401:
85
+ raise AuthError(f"Unauthorized (body: {response.text[:200]})")
86
+ if response.status_code == 404:
87
+ from dwf_cli.core.errors import NotFoundError
88
+
89
+ raise NotFoundError(f"Not found: {path}")
90
+ if response.status_code >= 400:
91
+ raise APIError(response.status_code, response.text)
92
+ return response
93
+
94
+ def raw_request(
95
+ self,
96
+ method: str,
97
+ path: str,
98
+ *,
99
+ auth: httpx.Auth | None = None,
100
+ ) -> httpx.Response:
101
+ return self._http.request(method, path, auth=auth)
102
+
103
+ def get(self, path: str, **kwargs: Any) -> Any:
104
+ return self._request("GET", path, **kwargs)
105
+
106
+ def post(self, path: str, **kwargs: Any) -> Any:
107
+ return self._request("POST", path, **kwargs)
108
+
109
+ def put(self, path: str, **kwargs: Any) -> Any:
110
+ return self._request("PUT", path, **kwargs)
111
+
112
+ def delete(self, path: str, **kwargs: Any) -> None:
113
+ self._request("DELETE", path, **kwargs)
@@ -0,0 +1,458 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ import httpx
8
+ from dwf_cli.api.client import APIClient
9
+
10
+
11
+ class ModelType(str, Enum):
12
+ entity = "entity"
13
+ e = "e"
14
+ relation = "relation"
15
+ r = "r"
16
+ external = "external"
17
+ x = "x"
18
+
19
+
20
+ _MODEL_TYPE_PATHS: dict[str, str] = {
21
+ "entity": "/dwf/v1/meta/entities-internal",
22
+ "e": "/dwf/v1/meta/entities-internal",
23
+ "relation": "/dwf/v1/meta/relations",
24
+ "r": "/dwf/v1/meta/relations",
25
+ "external": "/dwf/v1/meta/entities-external",
26
+ "x": "/dwf/v1/meta/entities-external",
27
+ }
28
+
29
+ _ENTITY_VALUES = {"entity", "e"}
30
+
31
+
32
+ def list_models(
33
+ client: APIClient,
34
+ model_type: ModelType,
35
+ *,
36
+ page: int = 0,
37
+ page_size: int = 25,
38
+ with_page_info: bool = True,
39
+ is_system: bool = False,
40
+ search: str | None = None,
41
+ search_fields: list[str] | None = None,
42
+ ) -> dict[str, Any]:
43
+ path = _MODEL_TYPE_PATHS[model_type.value]
44
+ params: dict[str, Any] = {
45
+ "pageIndex": page,
46
+ "pageSize": page_size,
47
+ "withPageInfo": str(with_page_info).lower(),
48
+ }
49
+ if model_type.value in _ENTITY_VALUES:
50
+ params["isSystem"] = str(is_system).lower()
51
+ body: dict[str, Any] = {}
52
+ if search:
53
+ body["keyword"] = search
54
+ body["attributes"] = search_fields or [
55
+ "className",
56
+ "displayName",
57
+ "zoneName",
58
+ "note",
59
+ ]
60
+ return client.post(path, params=params, json=body)
61
+
62
+
63
+ def get_class(
64
+ client: APIClient,
65
+ name: str,
66
+ ) -> Any:
67
+ return client.get(f"/dwf/v1/meta/classes/{name}")
68
+
69
+
70
+ def create_entity(
71
+ client: APIClient,
72
+ classes: list[dict[str, Any]],
73
+ *,
74
+ with_parent_oid: bool = False,
75
+ ) -> Any:
76
+ return client.post(
77
+ "/dwf/v1/meta/entities-create",
78
+ params={"withParentOid": str(with_parent_oid).lower()},
79
+ json=classes,
80
+ )
81
+
82
+
83
+ def update_entity(
84
+ client: APIClient,
85
+ class_data: dict[str, Any],
86
+ ) -> Any:
87
+ return client.post("/dwf/v1/meta/entities-update", json=class_data)
88
+
89
+
90
+ def update_relation(
91
+ client: APIClient,
92
+ relation_data: dict[str, Any],
93
+ ) -> Any:
94
+ return client.post("/dwf/v1/meta/relation-update", json=relation_data)
95
+
96
+
97
+ def delete_class(
98
+ client: APIClient,
99
+ name: str,
100
+ *,
101
+ cascade: bool = False,
102
+ ) -> Any:
103
+ return client.post(
104
+ f"/dwf/v1/meta/classes-delete/{name}",
105
+ params={"cascade": str(cascade).lower()},
106
+ )
107
+
108
+
109
+ def get_relations_tree(client: APIClient) -> Any:
110
+ return client.get("/dwf/v1/meta/relations/tree")
111
+
112
+
113
+ def list_class_names(client: APIClient) -> Any:
114
+ return client.get("/dwf/v1/meta/class-names")
115
+
116
+
117
+ def get_attributes(
118
+ client: APIClient,
119
+ class_name: str,
120
+ *,
121
+ need_sys_attr: bool = False,
122
+ ) -> Any:
123
+ return client.get(
124
+ f"/dwf/v1/meta/entities/{class_name}/attributes",
125
+ params={"needSysAttr": str(need_sys_attr).lower()},
126
+ )
127
+
128
+
129
+ def list_attributes(
130
+ client: APIClient,
131
+ *,
132
+ keyword: str | None = None,
133
+ ) -> Any:
134
+ params: dict[str, str] = {}
135
+ if keyword:
136
+ params["keyword"] = keyword
137
+ return client.get("/dwf/v1/meta/attributes", params=params)
138
+
139
+
140
+ def list_objects(
141
+ client: APIClient,
142
+ class_name: str,
143
+ *,
144
+ page: int = 0,
145
+ page_size: int = 25,
146
+ with_page_info: bool = True,
147
+ condition: str | None = None,
148
+ order_condition: dict[str, str] | None = None,
149
+ return_attrs: list[str] | None = None,
150
+ ) -> Any:
151
+ body: dict[str, Any] = {}
152
+ if condition:
153
+ body["condition"] = condition
154
+ if order_condition:
155
+ body["orderCondition"] = order_condition
156
+ if return_attrs:
157
+ body["returnAttrs"] = return_attrs
158
+ body["startIndex"] = page * page_size
159
+ body["pageSize"] = page_size
160
+ return client.post(
161
+ f"/dwf/v1/omf/entities/{class_name}/objects",
162
+ params={
163
+ "pageIndex": page,
164
+ "pageSize": page_size,
165
+ "totalCount": str(with_page_info).lower(),
166
+ },
167
+ json=body,
168
+ )
169
+
170
+
171
+ def query_objects(
172
+ client: APIClient,
173
+ class_name: str,
174
+ body: dict[str, Any],
175
+ *,
176
+ page: int = 0,
177
+ page_size: int = 25,
178
+ with_page_info: bool = True,
179
+ ) -> Any:
180
+ body["startIndex"] = page * page_size
181
+ body["pageSize"] = page_size
182
+ return client.post(
183
+ f"/dwf/v1/omf/entities/{class_name}/objects",
184
+ params={
185
+ "pageIndex": page,
186
+ "pageSize": page_size,
187
+ "totalCount": str(with_page_info).lower(),
188
+ },
189
+ json=body,
190
+ )
191
+
192
+
193
+ def get_object(
194
+ client: APIClient,
195
+ class_name: str,
196
+ oid: str,
197
+ ) -> Any:
198
+ return client.post(f"/dwf/v1/omf/entities/{class_name}/objects/{oid}")
199
+
200
+
201
+ def create_objects(
202
+ client: APIClient,
203
+ class_name: str,
204
+ objects: list[dict[str, Any]],
205
+ ) -> Any:
206
+ return client.post(
207
+ f"/dwf/v1/omf/entities/{class_name}/objects-create",
208
+ json=objects,
209
+ )
210
+
211
+
212
+ def update_objects(
213
+ client: APIClient,
214
+ class_name: str,
215
+ objects: list[dict[str, Any]],
216
+ *,
217
+ force_update: bool = False,
218
+ ) -> Any:
219
+ return client.post(
220
+ f"/dwf/v1/omf/entities/{class_name}/objects-update",
221
+ params={"forceUpdate": str(force_update).lower()},
222
+ json=objects,
223
+ )
224
+
225
+
226
+ def delete_objects(
227
+ client: APIClient,
228
+ class_name: str,
229
+ oids: list[str],
230
+ ) -> Any:
231
+ return client.post(
232
+ f"/dwf/v1/omf/entities/{class_name}/objects-delete",
233
+ json=oids,
234
+ )
235
+
236
+
237
+ def count_objects(
238
+ client: APIClient,
239
+ class_name: str,
240
+ *,
241
+ condition: str | None = None,
242
+ ) -> Any:
243
+ body: dict[str, Any] = {}
244
+ if condition:
245
+ body["condition"] = condition
246
+ return client.post(
247
+ f"/dwf/v1/omf/entities/{class_name}/objects/count",
248
+ json=body,
249
+ )
250
+
251
+
252
+ def create_relations(
253
+ client: APIClient,
254
+ relations: list[dict[str, Any]],
255
+ ) -> Any:
256
+ return client.post("/dwf/v1/meta/relations-create", json=relations)
257
+
258
+
259
+ def create_external_class(
260
+ client: APIClient,
261
+ class_list: list[dict[str, Any]],
262
+ custom_sql_map: dict[str, str] | None = None,
263
+ ) -> Any:
264
+ body: dict[str, Any] = {"classList": class_list}
265
+ if custom_sql_map:
266
+ body["customSqlMap"] = custom_sql_map
267
+ return client.post("/dwf/v1/meta/external-entities-create", json=body)
268
+
269
+
270
+ def update_external_class(
271
+ client: APIClient,
272
+ class_list: list[dict[str, Any]],
273
+ custom_sql_map: dict[str, str] | None = None,
274
+ ) -> Any:
275
+ body: dict[str, Any] = {"classList": class_list}
276
+ if custom_sql_map:
277
+ body["customSqlMap"] = custom_sql_map
278
+ return client.post("/dwf/v1/meta/external-entities-update", json=body)
279
+
280
+
281
+ def create_attributes(
282
+ client: APIClient,
283
+ attributes: list[dict[str, Any]],
284
+ ) -> Any:
285
+ return client.post("/dwf/v1/meta/attributes-create", json=attributes)
286
+
287
+
288
+ def update_attribute(
289
+ client: APIClient,
290
+ attribute: dict[str, Any],
291
+ ) -> Any:
292
+ return client.post("/dwf/v1/meta/attributes-update", json=attribute)
293
+
294
+
295
+ def delete_attribute(
296
+ client: APIClient,
297
+ name: str,
298
+ *,
299
+ force: bool = False,
300
+ ) -> Any:
301
+ return client.post(
302
+ f"/dwf/v1/meta/attributes-delete/{name}",
303
+ params={"force": str(force).lower()},
304
+ )
305
+
306
+
307
+ def get_attribute(
308
+ client: APIClient,
309
+ name: str,
310
+ ) -> Any:
311
+ return client.get(f"/dwf/v1/meta/attributes/{name}")
312
+
313
+
314
+ def get_attribute_bind_classes(
315
+ client: APIClient,
316
+ name: str,
317
+ ) -> Any:
318
+ return client.get(f"/dwf/v1/meta/attributes/{name}/bind-classes")
319
+
320
+
321
+ def bind_attributes(
322
+ client: APIClient,
323
+ class_name: str,
324
+ attributes: list[dict[str, Any]],
325
+ *,
326
+ is_relation: bool = False,
327
+ ) -> Any:
328
+ prefix = "relations" if is_relation else "entities"
329
+ return client.post(
330
+ f"/dwf/v1/meta/{prefix}/{class_name}/attributes-bind",
331
+ json=attributes,
332
+ )
333
+
334
+
335
+ def unbind_attribute(
336
+ client: APIClient,
337
+ class_name: str,
338
+ attr_name: str,
339
+ *,
340
+ is_relation: bool = False,
341
+ ) -> Any:
342
+ prefix = "relations" if is_relation else "entities"
343
+ return client.post(
344
+ f"/dwf/v1/meta/{prefix}/{class_name}/attributes-untie/{attr_name}",
345
+ )
346
+
347
+
348
+ def get_import_uuid(
349
+ client: APIClient,
350
+ *,
351
+ update_if_exist: bool = False,
352
+ ) -> Any:
353
+ return client.get(
354
+ "/dwf/v1/importData/getImportDataUUID",
355
+ params={"updateIfExist": str(update_if_exist).lower()},
356
+ )
357
+
358
+
359
+ def import_data(
360
+ client: APIClient,
361
+ uuid: str,
362
+ sock_id: str,
363
+ file_path: str,
364
+ ) -> Any:
365
+ with open(file_path, "rb") as f:
366
+ return client.post(
367
+ "/dwf/v1/importData/importData",
368
+ params={"uuid": uuid, "sockID": sock_id},
369
+ files={"file": f},
370
+ )
371
+
372
+
373
+ def export_data(
374
+ client: APIClient,
375
+ class_name: str,
376
+ attributes: list[str],
377
+ *,
378
+ condition: str | None = None,
379
+ join_to_one: bool = False,
380
+ title_only: bool = False,
381
+ ) -> httpx.Response:
382
+ body: dict[str, Any] = {
383
+ "className": class_name,
384
+ "attributes": attributes,
385
+ }
386
+ if condition:
387
+ body["condition"] = condition
388
+ return client.download(
389
+ "/dwf/v1/exportData/exportData",
390
+ method="POST",
391
+ params={
392
+ "joinToOneSheet": str(join_to_one).lower(),
393
+ "titleOnly": str(title_only).lower(),
394
+ },
395
+ json=body,
396
+ )
397
+
398
+
399
+ def upload_excel(
400
+ client: APIClient,
401
+ file_path: str,
402
+ ) -> Any:
403
+ with open(file_path, "rb") as f:
404
+ return client.post(
405
+ "/dwf/v1/quickStartFromExcel/uploadExcel",
406
+ files={"excelFile": f},
407
+ )
408
+
409
+
410
+ def confirm_excel(
411
+ client: APIClient,
412
+ info: dict[str, Any],
413
+ ) -> Any:
414
+ return client.post(
415
+ "/dwf/v1/quickStartFromExcel/confirmClassInfo",
416
+ json=info,
417
+ )
418
+
419
+
420
+ def check_class_name_valid(
421
+ client: APIClient,
422
+ class_name: str,
423
+ ) -> bool:
424
+ result = client.get(
425
+ "/dwf/v1/quickStartFromExcel/checkClassNameValid",
426
+ params={"className": class_name},
427
+ )
428
+ if isinstance(result, dict):
429
+ data = result.get("data", {})
430
+ if isinstance(data, dict):
431
+ return data.get("valid", False) is True
432
+ return False
433
+
434
+
435
+ def check_attribute_type_valid(
436
+ client: APIClient,
437
+ attribute_name: str,
438
+ value_type: str,
439
+ ) -> bool:
440
+ result = client.get(
441
+ "/dwf/v1/quickStartFromExcel/checkAttributeTypeValid",
442
+ params={"attributeName": attribute_name, "valueType": value_type},
443
+ )
444
+ if isinstance(result, dict):
445
+ data = result.get("data", {})
446
+ if isinstance(data, dict):
447
+ return data.get("valid", False) is True
448
+ return False
449
+
450
+
451
+ def delete_temp_excel(
452
+ client: APIClient,
453
+ uuid: str,
454
+ ) -> Any:
455
+ return client.get(
456
+ "/dwf/v1/quickStartFromExcel/deleteTempExcelFile",
457
+ params={"uuid": uuid},
458
+ )