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.
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/PKG-INFO +1 -1
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/pyproject.toml +1 -1
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/bank.py +30 -6
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/client.py +47 -36
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/company.py +18 -5
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/item.py +9 -1
- fiuai_sdk_python-0.3.6/src/fiuai_sdk_python/resp.py +193 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/setup.py +68 -20
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/type.py +1 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/util.py +15 -2
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/.gitignore +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/CHANGELOG.md +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/LICENSE +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/README.md +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/__init__.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/__init__.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/header.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/helper.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/test_auth.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/auth/type.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/const.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/error.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/perm.py +0 -0
- {fiuai_sdk_python-0.3.4 → fiuai_sdk_python-0.3.6}/src/fiuai_sdk_python/profile.py +0 -0
- {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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) ->
|
|
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 = "") ->
|
|
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) ->
|
|
130
|
+
def get_tenant(self) -> ApiResponse:
|
|
130
131
|
return self.headers.x_fiuai_auth_tenant_id
|
|
131
132
|
|
|
132
|
-
def get_company(self) ->
|
|
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) ->
|
|
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
|
-
|
|
150
|
+
profile_response = self.post_process(res)
|
|
150
151
|
|
|
151
|
-
if
|
|
152
|
-
|
|
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
|
-
|
|
161
|
+
return profile_response
|
|
155
162
|
|
|
156
163
|
|
|
157
164
|
|
|
158
|
-
def
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
if not
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
111
|
-
reqd =
|
|
112
|
-
|
|
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 =
|
|
116
|
-
length =
|
|
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
|
|
157
|
-
read_only=True if
|
|
158
|
-
reqd=True if
|
|
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
|
|
162
|
-
in_list_view=True if
|
|
163
|
-
in_mobile_view=True if
|
|
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
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|