fiuai-sdk-python 0.3.4__tar.gz → 0.3.6__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.
Files changed (25) hide show
  1. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/PKG-INFO +1 -1
  2. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/pyproject.toml +1 -1
  3. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/bank.py +30 -6
  4. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/client.py +47 -36
  5. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/company.py +18 -5
  6. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/item.py +9 -1
  7. fiuai_sdk_python-0.3.6/src/fiuai_sdk_python/resp.py +193 -0
  8. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/setup.py +68 -20
  9. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/type.py +1 -0
  10. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/util.py +15 -2
  11. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/.gitignore +0 -0
  12. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/CHANGELOG.md +0 -0
  13. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/LICENSE +0 -0
  14. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/README.md +0 -0
  15. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/__init__.py +0 -0
  16. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/__init__.py +0 -0
  17. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/header.py +0 -0
  18. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/helper.py +0 -0
  19. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/test_auth.py +0 -0
  20. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/type.py +0 -0
  21. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/const.py +0 -0
  22. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/error.py +0 -0
  23. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/perm.py +0 -0
  24. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/profile.py +0 -0
  25. {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/test_all.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiuai_sdk_python
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: FiuAI Python SDK - 企业级AI服务集成开发工具包
5
5
  Project-URL: Homepage, https://github.com/fiuai/fiuai-sdk-python
6
6
  Project-URL: Documentation, https://github.com/fiuai/fiuai-sdk-python#readme
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fiuai_sdk_python"
3
- version = "0.3.4"
3
+ version = "0.3.6"
4
4
  description = "FiuAI Python SDK - 企业级AI服务集成开发工具包"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -46,33 +46,45 @@ def get_bank_account(client: FiuaiSDK, bank_account_no: str) -> Optional[Company
46
46
  logger.error(f"bank_account_no is empty, return None")
47
47
  return None
48
48
 
49
- _bank_account = client.internal_get(
49
+ _bank_account_response = client.internal_get(
50
50
  doctype="Bank Account",
51
51
  name=bank_account_no,
52
52
  fields=["name","bank_account_no", "account_name", "bank", "opening_bank_branch"]
53
53
  )
54
54
 
55
55
 
56
+ if not _bank_account_response.is_success():
57
+ return CompanyBankAccountSearchResult(exists=False)
58
+
59
+ _bank_account = _bank_account_response.data
56
60
  if not _bank_account:
57
61
  return CompanyBankAccountSearchResult(exists=False)
58
62
 
59
63
 
60
64
  # 获取银行信息
61
- _bank_detail = client.internal_get(
65
+ _bank_detail_response = client.internal_get(
62
66
  doctype="Bank",
63
67
  name=_bank_account["bank"],
64
68
  fields=["name", "bank_name"]
65
69
  )
70
+ if not _bank_detail_response.is_success():
71
+ return CompanyBankAccountSearchResult(exists=False)
72
+
73
+ _bank_detail = _bank_detail_response.data
66
74
  if not _bank_detail:
67
75
  logger.error(f"bank is not found by id {_bank_account["bank"]}")
68
76
  return CompanyBankAccountSearchResult(exists=False)
69
77
 
70
78
  # 获取支行信息
71
- _bank_branch = client.internal_get(
79
+ _bank_branch_response = client.internal_get(
72
80
  doctype="Bank Branch",
73
81
  name=_bank_account["opening_bank_branch"],
74
82
  fields=["name", "bank_branch_name"]
75
83
  )
84
+ if not _bank_branch_response.is_success():
85
+ return CompanyBankAccountSearchResult(exists=False)
86
+
87
+ _bank_branch = _bank_branch_response.data
76
88
  if not _bank_branch:
77
89
  logger.error(f"bank branch is not found by id {_bank_account["opening_bank_branch"]}")
78
90
  return CompanyBankAccountSearchResult(exists=False)
@@ -93,7 +105,7 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
93
105
  """
94
106
  根据银行账号id获取银行账号信息
95
107
  """
96
- resp = client.internal_get_list(
108
+ resp_response = client.internal_get_list(
97
109
  doctype="Bank Account",
98
110
  filters=[["name", "=", bank_id]],
99
111
  fields=[
@@ -105,6 +117,10 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
105
117
  ],
106
118
  )
107
119
 
120
+ if not resp_response.is_success():
121
+ return None
122
+
123
+ resp = resp_response.data
108
124
  if not resp:
109
125
  return None
110
126
 
@@ -113,11 +129,15 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
113
129
  # 获取支行信息
114
130
  bank_branch = ""
115
131
  if resp["opening_bank_branch"] != "":
116
- _bank_branch = client.internal_get(
132
+ _bank_branch_response = client.internal_get(
117
133
  doctype="Bank Branch",
118
134
  name=resp["opening_bank_branch"],
119
135
  fields=["name", "bank_branch_name"]
120
136
  )
137
+ if not _bank_branch_response.is_success():
138
+ return None
139
+
140
+ _bank_branch = _bank_branch_response.data
121
141
  if not _bank_branch:
122
142
  logger.error(f"bank branch is not found by id {resp["opening_bank_branch"]}")
123
143
  return None
@@ -125,12 +145,16 @@ def get_bank_account_by_bank_id(client: FiuaiSDK, bank_id: str) -> BankAccount|N
125
145
  bank_branch = _bank_branch["bank_branch_name"]
126
146
 
127
147
  # 获取银行信息
128
- _bank_detail = client.internal_get(
148
+ _bank_detail_response = client.internal_get(
129
149
  doctype="Bank",
130
150
  name=resp["bank"],
131
151
  fields=["name", "bank_name"]
132
152
  )
133
153
 
154
+ if not _bank_detail_response.is_success():
155
+ return None
156
+
157
+ _bank_detail = _bank_detail_response.data
134
158
  if not _bank_detail:
135
159
  logger.error(f"bank is not found by id {resp["bank"]}")
136
160
  return None
@@ -10,6 +10,7 @@ from logging import getLogger
10
10
  from .type import UserProfile
11
11
  from .profile import UserProfileInfo
12
12
  from .auth import AuthHeader
13
+ from .resp import parse_response, ApiResponse
13
14
 
14
15
 
15
16
  logger = getLogger(__name__)
@@ -100,12 +101,12 @@ class FiuaiSDK(object):
100
101
  # self.logout()
101
102
 
102
103
 
103
- def get_avaliable_company(self, page: int=1, page_size: int=20) -> str:
104
+ def get_avaliable_company(self, page: int=1, page_size: int=20) -> ApiResponse:
104
105
  r = self.client.get(self.url + "/api/method/fiuai.network.doctype.company.company.get_available_companies", params={"page": page, "page_size": page_size})
105
106
 
106
107
  return self.post_process(r)
107
108
 
108
- def swith_company(self, tenant: str = "", company: str = "") -> bool:
109
+ def swith_company(self, tenant: str = "", company: str = "") -> ApiResponse:
109
110
 
110
111
  self.headers.x_fiuai_auth_tenant_id = tenant
111
112
  self.headers.x_fiuai_current_company = company
@@ -126,10 +127,10 @@ class FiuaiSDK(object):
126
127
  # else:
127
128
  # return True
128
129
 
129
- def get_tenant(self) -> str:
130
+ def get_tenant(self) -> ApiResponse:
130
131
  return self.headers.x_fiuai_auth_tenant_id
131
132
 
132
- def get_company(self) -> str:
133
+ def get_company(self) -> ApiResponse:
133
134
  return self.headers.x_fiuai_current_company
134
135
 
135
136
  # def get_v2_api(self, uri, params={}):
@@ -139,31 +140,41 @@ class FiuaiSDK(object):
139
140
  # return self.post_process(res)
140
141
 
141
142
 
142
- def get_user_profile_info(self, user_id: str=None) -> UserProfileInfo:
143
+ def get_user_profile_info(self, user_id: str=None) -> ApiResponse:
143
144
  """获取详细的用户信息"""
144
145
  if not user_id:
145
146
  user_id = self.headers.x_fiuai_user
146
147
 
147
148
  res = self.client.get(self.url + f"/api/v2/internal/user/profile/{user_id}", headers=self.headers.model_dump(by_alias=True))
148
149
 
149
- profile = self.post_process(res)
150
+ profile_response = self.post_process(res)
150
151
 
151
- if not profile:
152
- return None
152
+ if profile_response.is_success():
153
+ try:
154
+ profile_response.data = UserProfileInfo.model_validate(profile_response.data)
155
+ except Exception as e:
156
+ profile_response.error = str(e)
157
+ profile_response.error_code = "PROFILE_FORMAT_ERROR"
158
+ return profile_response
159
+ return profile_response
153
160
  else:
154
- return UserProfileInfo.model_validate(profile)
161
+ return profile_response
155
162
 
156
163
 
157
164
 
158
- def internal_post(self, uri, postdata={}):
165
+ def internal_post_req(self, uri, postdata={}) -> ApiResponse:
159
166
  res = self.client.post(self.url + '/api/v2/internal/' + uri.lstrip('/'), data=postdata, headers=self.headers.model_dump(by_alias=True))
160
167
  return self.post_process(res)
168
+
169
+ def internal_get_req(self, uri, params={}) -> ApiResponse:
170
+ res = self.client.get(self.url + '/api/v2/internal/' + uri.lstrip('/'), params=params, headers=self.headers.model_dump(by_alias=True))
171
+ return self.post_process(res)
161
172
 
162
- def internal_create(self, data={}):
173
+ def internal_create(self, data={}) -> ApiResponse:
163
174
  res = self.client.post(self.url + '/api/v2/internal/doctype/create', data={"data":json.dumps(data, ensure_ascii=False)}, headers=self.headers.model_dump(by_alias=True))
164
175
  return self.post_process(res)
165
176
 
166
- def internal_get(self, doctype, name, fields=None, filters=None):
177
+ def internal_get(self, doctype, name, fields=None, filters=None) -> ApiResponse:
167
178
  d = {
168
179
  "doctype": doctype,
169
180
  "name": name,
@@ -175,7 +186,7 @@ class FiuaiSDK(object):
175
186
  res = self.client.get(self.url + '/api/v2/internal/doctype/get', params=d, headers=self.headers.model_dump(by_alias=True))
176
187
  return self.post_process(res)
177
188
 
178
- def internal_get_list(self, doctype, filters=None, fields=None, limit_start=0, limit_page_length=20, order_by=None):
189
+ def internal_get_list(self, doctype, filters=None, fields=None, limit_start=0, limit_page_length=20, order_by=None) -> ApiResponse:
179
190
  d = {
180
191
  "doctype": doctype,
181
192
  "limit_start":limit_start,
@@ -194,47 +205,47 @@ class FiuaiSDK(object):
194
205
  return self.post_process(res)
195
206
 
196
207
 
197
- def internal_update(self, data={}):
208
+ def internal_update(self, data={}) -> ApiResponse:
198
209
  res = self.client.post(self.url + '/api/v2/internal/doctype/update', data={"data":json.dumps(data, ensure_ascii=False)}, headers=self.headers.model_dump(by_alias=True))
199
210
  return self.post_process(res)
200
211
 
201
- def internal_delete(self, doctype, name):
212
+ def internal_delete(self, doctype, name) -> ApiResponse:
202
213
  res = self.client.post(self.url + '/api/v2/internal/doctype/delete', data={"doctype": doctype, "name":name}, headers=self.headers.model_dump(by_alias=True))
203
214
  return self.post_process(res)
204
215
 
205
- def internal_submit(self, doctype, name):
216
+ def internal_submit(self, doctype, name) -> ApiResponse:
206
217
  res = self.client.post(self.url + '/api/v2/internal/doctype/submit', data={"doctype": doctype, "name":name}, headers=self.headers.model_dump(by_alias=True))
207
218
  return self.post_process(res)
208
219
 
209
- def internal_cancel(self, doctype, name):
220
+ def internal_cancel(self, doctype, name) -> ApiResponse:
210
221
  res = self.client.post(self.url + '/api/v2/internal/doctype/cancel', data={"doctype": doctype, "name":name}, headers=self.headers.model_dump(by_alias=True))
211
222
  return self.post_process(res)
212
223
 
213
224
 
214
- def get_meta(self, doctype: str):
225
+ def get_meta(self, doctype: str) -> ApiResponse:
215
226
  res = self.client.get(self.url + '/api/v2/internal/doctype/meta/' + doctype, headers=self.headers.model_dump(by_alias=True))
216
227
 
217
228
  return self.post_process(res)
218
229
 
219
230
 
220
- def post_process(self, response):
221
- if not response:
222
- return None
223
- try:
224
- rjson = response.json()
225
- except ValueError:
226
- raise
227
-
228
- if rjson and ('exc' in rjson) and rjson['exc']:
229
- raise FiuaiGeneralError(rjson['exc'])
230
- if 'message' in rjson:
231
- return rjson['message']
232
- elif 'data' in rjson:
233
- return rjson['data']
234
- elif 'errors' in rjson:
235
- raise FiuaiGeneralError(rjson['errors'])
236
- else:
237
- return None
231
+ def post_process(self, response) -> ApiResponse:
232
+ """
233
+ 处理API响应,使用结构化的错误处理系统
234
+
235
+ Args:
236
+ response: httpx响应对象
237
+
238
+ Returns:
239
+ Any: 成功时返回数据,失败时抛出异常
240
+
241
+ Raises:
242
+ FiuaiGeneralError: 当API返回错误时
243
+ FiuaiAuthError: 当认证相关错误时
244
+ """
245
+ # 使用resp.py中的统一解析函数
246
+ api_response = parse_response(response)
247
+
248
+ return api_response
238
249
 
239
250
 
240
251
  def get_client(username: str, auth_tenant_id: str, current_company: str, impersonation: str=None)-> FiuaiSDK:
@@ -36,19 +36,27 @@ def get_target_company_by_name(client: FiuaiSDK, full_name: str, target_side: Li
36
36
  """
37
37
  connection_type = "Customer" if target_side == "buyer" else "Supplier"
38
38
 
39
- _lookup = client.internal_get_list(
39
+ _lookup_response = client.internal_get_list(
40
40
  doctype="Company Lookup",
41
41
  fields=["target_company_id", "unique_no"],
42
42
  filters=[["company_name", "=", full_name]])
43
+ if not _lookup_response.is_success():
44
+ return TargetCompanySearchResult(name="", full_name="", unique_no="", exists=False, in_network=False, comment="该公司不存在")
45
+
46
+ _lookup = _lookup_response.data
43
47
  if not _lookup:
44
48
  logger.debug(f"get_target_company_by_name: {full_name} not found")
45
49
  return TargetCompanySearchResult(name="", full_name="", unique_no="", exists=False, in_network=False, comment="该公司不存在")
46
50
 
47
51
 
48
- _network = client.internal_get_list(
52
+ _network_response = client.internal_get_list(
49
53
  doctype="Company Network",
50
54
  fields=["name", "target_company_id", "target_company"],
51
55
  filters=[["target_company_id", "=", _lookup[0]["target_company_id"]], ["connection_type", "=", connection_type]])
56
+ if not _network_response.is_success():
57
+ return TargetCompanySearchResult(name="", full_name="", unique_no="", exists=True, in_network=False, comment="该公司不在本方网络中")
58
+
59
+ _network = _network_response.data
52
60
  if not _network:
53
61
  logger.debug(f"get_target_company_by_name: {full_name} is not in network")
54
62
  return TargetCompanySearchResult(name="", full_name="", unique_no="", exists=True, in_network=False, comment="该公司不在本方网络中")
@@ -70,7 +78,7 @@ def get_company_profile(client: FiuaiSDK, auth_company_id: str)-> CompanyProfile
70
78
  """
71
79
  获取公司信息
72
80
  """
73
- resp = client.internal_get_list(
81
+ resp_response = client.internal_get_list(
74
82
  doctype="Company",
75
83
  filters=[["name", "=", auth_company_id]],
76
84
  fields=[
@@ -92,16 +100,21 @@ def get_company_profile(client: FiuaiSDK, auth_company_id: str)-> CompanyProfile
92
100
  ],
93
101
  )
94
102
 
103
+ if not resp_response.is_success():
104
+ return None
105
+
106
+ resp = resp_response.data
95
107
  if not resp:
96
108
  return None
97
109
 
98
110
  r = resp[0]
99
111
 
100
112
  if r["default_bank_account"]:
101
- rr = get_bank_account_by_bank_id(client, r["default_bank_account"])
102
- if not rr:
113
+ rr_response = get_bank_account_by_bank_id(client, r["default_bank_account"])
114
+ if not rr_response:
103
115
  logger.error(f"get_company_profile: default_bank_account {r['default_bank_account']} not found")
104
116
  return None
117
+
105
118
  else:
106
119
  rr = None
107
120
 
@@ -26,7 +26,7 @@ def load_item_data(client: FiuaiSDK, auth_tenant_id: str, auth_company_id: str)-
26
26
  从frappe获取item数据
27
27
  """
28
28
 
29
- item_list = client.internal_get_list(
29
+ item_list_response = client.internal_get_list(
30
30
  doctype="Item",
31
31
  filters=[
32
32
  ["disabled", "=", 0],
@@ -35,4 +35,12 @@ def load_item_data(client: FiuaiSDK, auth_tenant_id: str, auth_company_id: str)-
35
35
  ],
36
36
  fields=["name", "item_code", "item_name", "stock_uom", "description"]
37
37
  )
38
+
39
+ if not item_list_response.is_success():
40
+ return []
41
+
42
+ item_list = item_list_response.data
43
+ if not item_list:
44
+ return []
45
+
38
46
  return [Item(name=item["name"], item_code=item["item_code"], item_name=item["item_name"], stock_uom=item["stock_uom"], description=item["description"] if item["description"] else "") for item in item_list]
@@ -0,0 +1,193 @@
1
+ # -- coding: utf-8 --
2
+ # Project: fiuai_sdk_python
3
+ # Created Date: 2025 10 Mo
4
+ # Author: liming
5
+ # Email: lmlala@aliyun.com
6
+ # Copyright (c) 2025 FiuAI
7
+
8
+ from typing import List, Dict, Any, Optional, Union
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class ApiResponse(BaseModel):
13
+ """API响应结构体"""
14
+ http_success: bool = Field(description="HTTP是否成功")
15
+ api_success: bool = Field(description="API业务是否成功")
16
+ status_code: int = Field(description="HTTP状态码")
17
+ data: Optional[Any] = Field(description="响应数据", default=None)
18
+ error_code: Optional[str] = Field(description="精简的错误码", default=None)
19
+ error: Optional[str] = Field(description="错误消息", default=None)
20
+
21
+ def is_success(self) -> bool:
22
+ """判断是否完全成功"""
23
+ return self.http_success and self.api_success
24
+
25
+
26
+ def parse_response(response) -> ApiResponse:
27
+ """
28
+ 解析HTTP响应,返回结构化的API响应
29
+
30
+ Args:
31
+ response: httpx响应对象
32
+
33
+ Returns:
34
+ ApiResponse: 结构化的API响应
35
+ """
36
+ # 检查响应是否存在
37
+ if not response:
38
+ return ApiResponse(
39
+ http_success=False,
40
+ api_success=False,
41
+ status_code=504,
42
+ error="Api no response",
43
+ error_code="API_NO_RESPONSE",
44
+ )
45
+
46
+
47
+ # 解析JSON响应
48
+ try:
49
+ # 检查HTTP状态码
50
+ http_success = 200 <= response.status_code < 300
51
+ except Exception as e:
52
+ return ApiResponse(
53
+ http_success=False,
54
+ api_success=False,
55
+ error=f"Invalid JSON response: {e}",
56
+ error_code="API_INVALID_JSON"
57
+ )
58
+
59
+ try:
60
+ response_data = response.json()
61
+ except Exception as e:
62
+ return ApiResponse(
63
+ http_success=True,
64
+ api_success=False,
65
+ error=f"Invalid JSON response: {e}",
66
+ error_code="API_INVALID_JSON"
67
+ )
68
+
69
+ # 检查API业务是否成功
70
+ api_success = _is_api_success(response_data)
71
+
72
+ if api_success:
73
+ # 成功响应,提取数据
74
+ data = _extract_success_data(response_data)
75
+ return ApiResponse(
76
+ http_success=http_success,
77
+ api_success=True,
78
+ status_code=response.status_code,
79
+ data=data
80
+ )
81
+ else:
82
+ # 失败响应,提取错误信息
83
+ error_msg, error_type = _extract_error_info(response_data)
84
+ return ApiResponse(
85
+ http_success=http_success,
86
+ api_success=False,
87
+ status_code=response.status_code,
88
+ error=error_msg,
89
+ error_code=error_type
90
+ )
91
+
92
+
93
+ def _is_api_success(response_data: Dict[str, Any]) -> bool:
94
+ """
95
+ 判断API业务是否成功
96
+
97
+ Args:
98
+ response_data: API响应数据
99
+
100
+ Returns:
101
+ bool: 是否成功
102
+ """
103
+ # 检查是否有错误
104
+ errors = response_data.get("errors", None)
105
+ if errors:
106
+ return False
107
+
108
+ # 检查是否有异常
109
+ exc = response_data.get("exc", None)
110
+ if exc:
111
+ return False
112
+
113
+ # 检查HTTP状态码
114
+ http_status_code = response_data.get("http_status_code", None)
115
+ if http_status_code is not None:
116
+ return 200 <= http_status_code < 300
117
+
118
+ # 默认认为有message或data字段就是成功
119
+ return response_data.get("message", None) is not None or response_data.get("data", None) is not None
120
+
121
+
122
+ def _extract_success_data(response_data: Dict[str, Any]) -> Any:
123
+ """
124
+ 从成功响应中提取数据
125
+
126
+ Args:
127
+ response_data: API响应数据
128
+
129
+ Returns:
130
+ Any: 提取的数据
131
+ """
132
+ return response_data.get("data", None)
133
+
134
+
135
+ def _extract_error_info(response_data: Dict[str, Any]) -> tuple[str, str]:
136
+ """
137
+ 从响应中提取错误信息
138
+
139
+ Args:
140
+ response_data: API响应数据
141
+
142
+ Returns:
143
+ tuple[str, str]: (错误消息, 错误类型)
144
+ """
145
+ # 处理errors字段
146
+ errors = response_data.get("errors", None)
147
+ if errors and isinstance(errors, list):
148
+ if errors:
149
+ # 获取所有错误消息,用换行符分割
150
+ error_messages = []
151
+ error_codes = []
152
+ for error in errors:
153
+ if isinstance(error, dict):
154
+ error_msg = error.get("message", "Unknown error")
155
+ error_code = error.get("type", "UnknownError")
156
+ error_messages.append(error_msg)
157
+ error_codes.append(error_code)
158
+
159
+ # 用换行符连接所有错误消息
160
+ combined_error_msg = "\n".join(error_messages)
161
+ # 使用第一个错误码作为主要错误码
162
+ primary_error_code = error_codes[0] if error_codes else "UnknownError"
163
+ return combined_error_msg, primary_error_code
164
+
165
+ # 处理exc字段(V1 API格式)
166
+ exc = response_data.get("exc", None)
167
+ if exc:
168
+ return str(exc), "API_EXCEPTION"
169
+
170
+ # 处理message字段中的错误
171
+ message = response_data.get("message", None)
172
+ if message and isinstance(message, str):
173
+ # 检查是否是错误消息
174
+ if any(keyword in message.lower() for keyword in ["error", "failed", "invalid", "unauthorized"]):
175
+ return message, "API_ERROR"
176
+
177
+ return "Unknown error occurred", "UnknownError"
178
+
179
+
180
+ def is_auth_error(error_type: str) -> bool:
181
+ """
182
+ 判断是否为认证相关错误
183
+
184
+ Args:
185
+ error_type: 错误类型
186
+
187
+ Returns:
188
+ bool: 是否为认证错误
189
+ """
190
+ auth_types = ["AuthenticationError", "PermissionError", "Unauthorized", "Forbidden"]
191
+ return any(auth_type in error_type for auth_type in auth_types)
192
+
193
+
@@ -23,13 +23,15 @@ from .type import (
23
23
  DocTypeMeta,
24
24
  DocField,
25
25
  DocFieldType,
26
- AiRecognitionValue
26
+
27
27
  )
28
28
  from typing import List
29
29
  from logging import getLogger
30
30
 
31
31
  from .client import FiuaiSDK
32
32
 
33
+ from .util import get_int_value
34
+
33
35
  logger = getLogger(__name__)
34
36
 
35
37
 
@@ -38,40 +40,68 @@ def load_uom_data(client: FiuaiSDK)-> List[UOM]:
38
40
  """
39
41
  从frappe获取uom数据
40
42
  """
41
- uom_list = client.internal_get_list(doctype="UOM", fields=["name", "uom_name", "common_code"])
43
+ uom_list_response = client.internal_get_list(doctype="UOM", fields=["name", "uom_name", "common_code"])
44
+ if not uom_list_response.is_success():
45
+ return []
46
+
47
+ uom_list = uom_list_response.data
48
+ if not uom_list:
49
+ return []
50
+
42
51
  return [UOM(name=uom["name"], uom_name=uom["uom_name"], common_code=uom["common_code"]) for uom in uom_list]
43
52
 
44
53
  def load_currency_data(client: FiuaiSDK)-> List[Currency]:
45
54
  """
46
55
  从frappe获取currency数据
47
56
  """
48
- currency_list = client.internal_get_list(
57
+ currency_list_response = client.internal_get_list(
49
58
  doctype="Currency",
50
59
  filters={"enabled": 1},
51
60
  fields=["name", "currency_name", "fraction_units", "smallest_currency_fraction_value"]
52
61
  )
62
+ if not currency_list_response.is_success():
63
+ return []
64
+
65
+ currency_list = currency_list_response.data
66
+ if not currency_list:
67
+ return []
68
+
53
69
  return [Currency(name=currency["name"], currency_name=currency["currency_name"], fraction_units=currency["fraction_units"], smallest_currency_fraction_value=currency["smallest_currency_fraction_value"]) for currency in currency_list]
54
70
 
55
71
  def load_country_data(client: FiuaiSDK)-> List[Country]:
56
72
  """
57
73
  从frappe获取country数据
58
74
  """
59
- country_list = client.internal_get_list(
75
+ country_list_response = client.internal_get_list(
60
76
  doctype="Country",
61
77
  filters={"name": ["in", USED_COUNTRY_LIST]},
62
78
  fields=["name", "code"]
63
79
  )
80
+ if not country_list_response.is_success():
81
+ return []
82
+
83
+ country_list = country_list_response.data
84
+ if not country_list:
85
+ return []
86
+
64
87
  return [Country(name=country["name"], code=country["code"]) for country in country_list]
65
88
 
66
89
  def load_language_data(client: FiuaiSDK)-> List[Language]:
67
90
  """
68
91
  从frappe获取language数据
69
92
  """
70
- language_list = client.internal_get_list(
93
+ language_list_response = client.internal_get_list(
71
94
  doctype="Language",
72
95
  filters={"name": ["in", USED_LANGUAGE_LIST]},
73
96
  fields=["name", "language_name"]
74
97
  )
98
+ if not language_list_response.is_success():
99
+ return []
100
+
101
+ language_list = language_list_response.data
102
+ if not language_list:
103
+ return []
104
+
75
105
  return [Language(name=language["name"], language_name=language["language_name"]) for language in language_list]
76
106
 
77
107
  def load_doctype_meta(client: FiuaiSDK, doctype: str, max_api_retry: int = 3, only_has_prompt: bool = True, show_hidden: bool = False)-> DocTypeMeta:
@@ -92,9 +122,15 @@ def load_doctype_meta(client: FiuaiSDK, doctype: str, max_api_retry: int = 3, on
92
122
 
93
123
  return doc_meta
94
124
 
95
- def _get_meta(client: FiuaiSDK, doctype: str, only_has_prompt: bool = True, show_hidden: bool = False) -> DocTypeMeta:
125
+ def _get_meta(client: FiuaiSDK, doctype: str, only_has_prompt: bool = True, show_hidden: bool = False) -> DocTypeMeta | None:
96
126
  try:
97
- m = client.get_meta(doctype)
127
+ m_response = client.get_meta(doctype)
128
+ if not m_response.is_success():
129
+ return None
130
+
131
+ m = m_response.data
132
+ if not m:
133
+ return None
98
134
 
99
135
  if m is None:
100
136
  return None
@@ -107,14 +143,22 @@ def _get_meta(client: FiuaiSDK, doctype: str, only_has_prompt: bool = True, show
107
143
  field_type = _f["fieldtype"]
108
144
  field_prompt = _f.get("field_prompt", None)
109
145
  options_str = _f.get("options", None)
110
- hidden = _f.get("hidden", 0)
111
- reqd = _f.get("reqd", 0)
112
- read_only = _f.get("read_only", 0)
146
+ hidden = get_int_value("hidden", _f)
147
+ reqd = get_int_value("reqd", _f)
148
+ mandatory = get_int_value("mandatory", _f)
149
+ read_only = get_int_value("read_only", _f)
150
+ in_list_view = get_int_value("in_list_view", _f)
151
+ in_mobile_view = get_int_value("in_mobile_view", _f)
113
152
  description = _f.get("description", "")
153
+ custom_query = _f.get("custom_query", None)
114
154
  link_filters = _f.get("link_filters", None)
115
- precision = _f.get("precision", None)
116
- length = _f.get("length", None)
155
+ precision = get_int_value("precision", _f)
156
+ length = get_int_value("length", _f)
117
157
 
158
+
159
+ ### 格式转化
160
+
161
+
118
162
  options = []
119
163
 
120
164
  match field_type:
@@ -148,19 +192,21 @@ def _get_meta(client: FiuaiSDK, doctype: str, only_has_prompt: bool = True, show
148
192
  except ValueError:
149
193
  # 如果找不到对应的枚举值,使用 Data 作为默认值
150
194
  fieldtype_enum = DocFieldType.Data
151
-
195
+
196
+ print(_f)
152
197
  _fields.append(DocField(
153
198
  fieldname=field_name,
154
199
  description=description,
155
200
  fieldtype=fieldtype_enum,
156
- hidden=True if _f.get("hidden", 0) == 1 else False,
157
- read_only=True if _f.get("read_only", 0) == 1 else False,
158
- reqd=True if _f.get("reqd", 0) == 1 else False,
201
+ hidden=True if hidden == 1 else False,
202
+ read_only=True if read_only == 1 else False,
203
+ reqd=True if reqd == 1 else False,
159
204
  options=options,
160
205
  field_prompt=field_prompt,
161
- mandatory=True if _f.get("mandatory", 0) == 1 else False,
162
- in_list_view=True if _f.get("in_list_view", 0) == 1 else False,
163
- in_mobile_view=True if _f.get("in_mobile_view", 0) == 1 else False,
206
+ mandatory=True if mandatory == 1 else False,
207
+ in_list_view=True if in_list_view == 1 else False,
208
+ in_mobile_view=True if in_mobile_view == 1 else False,
209
+ custom_query=custom_query,
164
210
  link_filters=link_filters,
165
211
  precision=precision,
166
212
  length=length,
@@ -180,7 +226,7 @@ def _get_meta(client: FiuaiSDK, doctype: str, only_has_prompt: bool = True, show
180
226
  )
181
227
  return doc_meta
182
228
  except Exception as e:
183
- logger.error(f"load doctype {doctype} meta failed: {e.args}")
229
+ logger.error(f"load doctype {doctype} meta failed: {e}")
184
230
  return None
185
231
 
186
232
 
@@ -191,5 +237,7 @@ def load_all_allowed_doctype_meta(client: FiuaiSDK, only_has_prompt: bool = True
191
237
  r = []
192
238
  for _d in AI_ALLOWED_DOCTYPE_META:
193
239
  m = load_doctype_meta(client, _d, only_has_prompt, show_hidden)
240
+ if not m:
241
+ continue
194
242
  r.append(m)
195
243
  return r
@@ -192,6 +192,7 @@ class DocField(BaseModel):
192
192
  read_only: bool = Field(description="字段是否只读")
193
193
  fieldtype: DocFieldType = Field(description="字段类型,比如Data,Float,Link,etc.")
194
194
  options: Optional[List[str]] = Field(description="字段选项,Select类型的字段的枚举值, Link类型字段的Link目标表名", default=None)
195
+ custom_query: Optional[str] = Field(description="自定义查询的方法名", default=None)
195
196
  link_filters: Optional[str] = Field(description="Link字段过滤条件,json字符串", default=None)
196
197
  length: Optional[int] = Field(description="字段长度限制", default=None)
197
198
  precision: Optional[int] = Field(description="浮点数类型时的精度", default=None)
@@ -8,7 +8,7 @@
8
8
 
9
9
 
10
10
  import logging
11
- from typing import List, Optional
11
+ from typing import List, Optional, Dict, Any
12
12
  from .type import ClientConfig
13
13
 
14
14
  logger = logging.getLogger(__name__)
@@ -87,4 +87,17 @@ def init_fiuai(
87
87
  timeout: 请求超时时间
88
88
  verify: 是否验证 SSL 证书
89
89
  """
90
- _config.init(url, max_api_retry, timeout, verify)
90
+ _config.init(url, max_api_retry, timeout, verify)
91
+
92
+
93
+ ###### tools
94
+
95
+ def get_int_value(key: str, data: Dict[str, Any]) -> int | None:
96
+ """
97
+ 获取int类型的值
98
+ """
99
+ d = data.get(key, None)
100
+ if not d:
101
+ return None
102
+
103
+ return int(d)