fiuai-sdk-python 0.4.2__tar.gz → 0.4.4__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.4.2 → fiuai_sdk_python-0.4.4}/PKG-INFO +1 -1
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/pyproject.toml +1 -1
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/header.py +3 -1
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/helper.py +18 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/type.py +2 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/client.py +2 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/context.py +2 -2
- fiuai_sdk_python-0.4.4/src/fiuai_sdk_python/resp.py +241 -0
- fiuai_sdk_python-0.4.2/src/fiuai_sdk_python/resp.py +0 -198
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/.gitignore +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/CHANGELOG.md +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/LICENSE +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/README.md +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/__init__.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/__init__.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/bank.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/company.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/const.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/error.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/examples/fastapi_integration.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/item.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/perm.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/profile.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/setup.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/type.py +0 -0
- {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fiuai_sdk_python
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
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
|
|
@@ -31,6 +31,7 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
|
|
|
31
31
|
impersonation = headers.get("x-fiuai-impersonation", "")
|
|
32
32
|
unique_no = headers.get("x-fiuai-unique-no", "")
|
|
33
33
|
trace_id = headers.get("x-fiuai-trace-id", "")
|
|
34
|
+
client = headers.get("x-fiuai-client", "unknown")
|
|
34
35
|
|
|
35
36
|
# 验证必需字段
|
|
36
37
|
if not user_id:
|
|
@@ -57,7 +58,8 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
|
|
|
57
58
|
current_company=current_company[0],
|
|
58
59
|
impersonation=impersonation,
|
|
59
60
|
company_unique_no=unique_no,
|
|
60
|
-
trace_id=trace_id
|
|
61
|
+
trace_id=trace_id,
|
|
62
|
+
client=client
|
|
61
63
|
)
|
|
62
64
|
|
|
63
65
|
except Exception as e:
|
|
@@ -143,6 +143,24 @@ def get_impersonation(
|
|
|
143
143
|
return auth_data.impersonation
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
def get_current_client(
|
|
147
|
+
request: Union[Request, Dict[str, str]],
|
|
148
|
+
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
149
|
+
) -> str:
|
|
150
|
+
"""
|
|
151
|
+
获取当前客户端标识
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
request: FastAPI Request 对象或包含认证数据的字典
|
|
155
|
+
engine: 请求引擎类型,默认为 "fastapi"
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
str: 客户端标识
|
|
159
|
+
"""
|
|
160
|
+
auth_data = get_auth_data(request, engine)
|
|
161
|
+
return auth_data.client
|
|
162
|
+
|
|
163
|
+
|
|
146
164
|
def is_impersonating(
|
|
147
165
|
request: Union[Request, Dict[str, str]],
|
|
148
166
|
engine: Literal["fastapi", "dict"] = "fastapi"
|
|
@@ -16,6 +16,7 @@ class AuthData(BaseModel):
|
|
|
16
16
|
impersonation: str = Field(description="当前代表的租户ID", default="")
|
|
17
17
|
company_unique_no: str = Field(description="当前公司唯一编号,正常情况等于current_company")
|
|
18
18
|
trace_id: str = Field(description="追踪ID", default="")
|
|
19
|
+
client: str = Field(description="客户端标识", default="unknown")
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
|
|
@@ -30,6 +31,7 @@ class AuthHeader(BaseModel):
|
|
|
30
31
|
x_fiuai_impersonation: str = Field(alias="x-fiuai-impersonation", default="", description="当前代表的租户ID")
|
|
31
32
|
x_fiuai_lang: str = Field(alias="x-fiuai-lang", default="zh", description="用户当前语言")
|
|
32
33
|
x_fiuai_trace_id: str = Field(alias="x-fiuai-trace-id", default="", description="追踪ID")
|
|
34
|
+
x_fiuai_client: str = Field(alias="x-fiuai-client", default="unknown", description="客户端标识")
|
|
33
35
|
accept_language: str = Field(alias="accept-language", default="zh", description="语言")
|
|
34
36
|
|
|
35
37
|
class Config:
|
|
@@ -94,6 +94,7 @@ class FiuaiSDK(object):
|
|
|
94
94
|
"x-fiuai-impersonation": context_headers.get("x-fiuai-impersonation", self.headers.x_fiuai_impersonation),
|
|
95
95
|
"x-fiuai-unique-no": company_unique_no_value,
|
|
96
96
|
"x-fiuai-trace-id": context_headers.get("x-fiuai-trace-id", self.headers.x_fiuai_trace_id),
|
|
97
|
+
"x-fiuai-client": context_headers.get("x-fiuai-client", self.headers.x_fiuai_client),
|
|
97
98
|
"x-fiuai-lang": context_headers.get("x-fiuai-lang", self.headers.x_fiuai_lang),
|
|
98
99
|
"accept-language": context_headers.get("accept-language", self.headers.accept_language),
|
|
99
100
|
})
|
|
@@ -112,6 +113,7 @@ class FiuaiSDK(object):
|
|
|
112
113
|
"x-fiuai-impersonation": self.headers.x_fiuai_impersonation,
|
|
113
114
|
"x-fiuai-unique-no": company_unique_no_value,
|
|
114
115
|
"x-fiuai-trace-id": self.headers.x_fiuai_trace_id,
|
|
116
|
+
"x-fiuai-client": self.headers.x_fiuai_client,
|
|
115
117
|
"x-fiuai-lang": self.headers.x_fiuai_lang,
|
|
116
118
|
"accept-language": self.headers.accept_language,
|
|
117
119
|
}
|
|
@@ -100,9 +100,9 @@ class RequestContext:
|
|
|
100
100
|
# 生成新的 trace id
|
|
101
101
|
processed_headers["x-fiuai-trace-id"] = str(uuid.uuid4())
|
|
102
102
|
|
|
103
|
-
# 确保 X-Fiuai-Client
|
|
103
|
+
# 确保 X-Fiuai-Client 字段存在(如果不存在则设为 "unknown")
|
|
104
104
|
if "x-fiuai-client" not in processed_headers:
|
|
105
|
-
processed_headers["x-fiuai-client"] = ""
|
|
105
|
+
processed_headers["x-fiuai-client"] = "unknown"
|
|
106
106
|
|
|
107
107
|
return processed_headers
|
|
108
108
|
|
|
@@ -0,0 +1,241 @@
|
|
|
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
|
+
import logging
|
|
9
|
+
from typing import List, Dict, Any, Optional, Union
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
# 配置日志
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# {'errors': [{'type': 'ValidationError', 'exception': 'Traceback (most recent call last):\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/fiuai_utils/auth.py", line 143, in wrapper\n r = func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/fiuai_utils/api/docs/docs.py", line 163, in create_internal_doc\n r = frappe.new_doc(**d).save()\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 416, in save\n return self._save(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 438, in _save\n return self.insert()\n ^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 346, in insert\n self._validate()\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 660, in _validate\n self._validate_mandatory()\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 994, in _validate_mandatory\n raise frappe.MandatoryError(\nfrappe.exceptions.MandatoryError: [Item, 7384919604195962880]: stock_uom\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/app.py", line 131, in application\n response = frappe.api.handle(request)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/api/__init__.py", line 51, in handle\n data = endpoint(**arguments)\n ^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/fiuai_utils/auth.py", line 147, in wrapper\n frappe.throw(title=_("INTERNAL_API_ERROR"),msg=_(f"{e}"))\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/__init__.py", line 651, in throw\n msgprint(\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/__init__.py", line 616, in msgprint\n _raise_exception()\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/__init__.py", line 567, in _raise_exception\n raise exc\nfrappe.exceptions.ValidationError: [Item, 7384919604195962880]: stock_uom\n', 'message': '[Item, 7384919604195962880]: stock_uom', 'title': 'INTERNAL_API_ERROR', 'indicator': 'red'}], 'messages': [{'message': '错误:商品缺少值:默认计量单位', 'title': '消息'}]}
|
|
17
|
+
|
|
18
|
+
class ApiResponse(BaseModel):
|
|
19
|
+
"""API响应结构体"""
|
|
20
|
+
http_success: bool = Field(description="HTTP是否成功")
|
|
21
|
+
api_success: bool = Field(description="API业务是否成功")
|
|
22
|
+
status_code: int = Field(description="HTTP状态码")
|
|
23
|
+
data: Optional[Any] = Field(description="响应数据", default=None)
|
|
24
|
+
error_code: Optional[List[str]] = Field(description="错误码列表,对应error的title", default=None)
|
|
25
|
+
error_message: Optional[List[str]] = Field(description="错误消息列表,对应error的message", default=None)
|
|
26
|
+
messages: Optional[List[Any]] = Field(description="消息,对应messages字段", default=None)
|
|
27
|
+
|
|
28
|
+
def is_success(self) -> bool:
|
|
29
|
+
"""判断是否完全成功"""
|
|
30
|
+
return self.http_success and self.api_success
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_response(response) -> ApiResponse:
|
|
34
|
+
"""
|
|
35
|
+
解析HTTP响应,返回结构化的API响应
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
response: httpx响应对象
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
ApiResponse: 结构化的API响应
|
|
42
|
+
"""
|
|
43
|
+
# 检查响应是否存在
|
|
44
|
+
if not response:
|
|
45
|
+
return ApiResponse(
|
|
46
|
+
http_success=False,
|
|
47
|
+
api_success=False,
|
|
48
|
+
status_code=504,
|
|
49
|
+
error_message=["Api no response"],
|
|
50
|
+
error_code=["API_NO_RESPONSE"],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# 解析JSON响应
|
|
55
|
+
try:
|
|
56
|
+
# 检查HTTP状态码
|
|
57
|
+
http_success = 200 <= response.status_code < 300
|
|
58
|
+
except Exception as e:
|
|
59
|
+
return ApiResponse(
|
|
60
|
+
http_success=False,
|
|
61
|
+
api_success=False,
|
|
62
|
+
status_code=response.status_code,
|
|
63
|
+
error_message=[f"Invalid JSON response: {e}"],
|
|
64
|
+
error_code=["API_INVALID_JSON"]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
response_data = response.json()
|
|
69
|
+
except Exception as e:
|
|
70
|
+
return ApiResponse(
|
|
71
|
+
http_success=True,
|
|
72
|
+
api_success=False,
|
|
73
|
+
status_code=response.status_code,
|
|
74
|
+
error_message=[f"Invalid JSON response: {e}"],
|
|
75
|
+
error_code=["API_INVALID_JSON"]
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# 检查API业务是否成功
|
|
79
|
+
api_success = _is_api_success(response_data)
|
|
80
|
+
|
|
81
|
+
if api_success:
|
|
82
|
+
# 成功响应,提取数据
|
|
83
|
+
data = _extract_success_data(response_data)
|
|
84
|
+
return ApiResponse(
|
|
85
|
+
http_success=http_success,
|
|
86
|
+
api_success=True,
|
|
87
|
+
status_code=response.status_code,
|
|
88
|
+
error_message=None,
|
|
89
|
+
error_code=None,
|
|
90
|
+
data=data,
|
|
91
|
+
messages=None
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
# 失败响应,提取错误信息
|
|
95
|
+
error_info = _extract_error_info(response_data)
|
|
96
|
+
return ApiResponse(
|
|
97
|
+
http_success=http_success,
|
|
98
|
+
api_success=False,
|
|
99
|
+
status_code=response.status_code,
|
|
100
|
+
error_message=error_info.get("messages", []),
|
|
101
|
+
error_code=error_info.get("codes", []),
|
|
102
|
+
data=None,
|
|
103
|
+
messages=None
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _is_api_success(response_data: Dict[str, Any]) -> bool:
|
|
108
|
+
"""
|
|
109
|
+
判断API业务是否成功
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
response_data: API响应数据
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
bool: 是否成功
|
|
116
|
+
"""
|
|
117
|
+
# 检查是否有错误
|
|
118
|
+
errors = response_data.get("errors", None)
|
|
119
|
+
if errors:
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
# 检查是否有异常
|
|
123
|
+
exc = response_data.get("exc", None)
|
|
124
|
+
if exc:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
# 检查HTTP状态码
|
|
128
|
+
http_status_code = response_data.get("http_status_code", None)
|
|
129
|
+
if http_status_code is not None:
|
|
130
|
+
return 200 <= http_status_code < 300
|
|
131
|
+
|
|
132
|
+
# 默认认为有message或data字段就是成功
|
|
133
|
+
return response_data.get("message", None) is not None or response_data.get("data", None) is not None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _extract_success_data(response_data: Dict[str, Any]) -> Any:
|
|
137
|
+
"""
|
|
138
|
+
从成功响应中提取数据
|
|
139
|
+
优先从 data 字段获取,如果没有则从 messages 字段获取
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
response_data: API响应数据
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Any: 提取的数据,如果都没有则返回 None
|
|
146
|
+
"""
|
|
147
|
+
# 优先从 data 字段获取
|
|
148
|
+
data = response_data.get("data", None)
|
|
149
|
+
if data is not None:
|
|
150
|
+
return data
|
|
151
|
+
|
|
152
|
+
# 如果 data 字段为空,尝试从 messages 字段获取
|
|
153
|
+
messages = response_data.get("messages", None)
|
|
154
|
+
if messages is not None:
|
|
155
|
+
return messages
|
|
156
|
+
|
|
157
|
+
# 都没有则返回 None
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _extract_error_info(response_data: Dict[str, Any]) -> Dict[str, List[str]]:
|
|
162
|
+
"""
|
|
163
|
+
从响应中提取错误信息
|
|
164
|
+
返回所有错误信息的列表
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
response_data: API响应数据
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Dict[str, List[str]]: {"messages": 错误消息列表, "codes": 错误码列表}
|
|
171
|
+
"""
|
|
172
|
+
error_messages = []
|
|
173
|
+
error_codes = []
|
|
174
|
+
|
|
175
|
+
# 处理errors字段
|
|
176
|
+
errors = response_data.get("errors", None)
|
|
177
|
+
if errors and isinstance(errors, list) and errors:
|
|
178
|
+
for error in errors:
|
|
179
|
+
if isinstance(error, dict):
|
|
180
|
+
error_msg = error.get("message", "Unknown error")
|
|
181
|
+
error_title = error.get("title", "UnknownError")
|
|
182
|
+
|
|
183
|
+
error_messages.append(error_msg)
|
|
184
|
+
error_codes.append(error_title)
|
|
185
|
+
|
|
186
|
+
# 记录异常详情到日志
|
|
187
|
+
exception = error.get("exception", None)
|
|
188
|
+
if exception:
|
|
189
|
+
logger.error(f"API Error Exception: {error_title} - {exception}")
|
|
190
|
+
|
|
191
|
+
# 记录其他错误信息到日志
|
|
192
|
+
indicator = error.get("indicator", "")
|
|
193
|
+
if indicator:
|
|
194
|
+
logger.error(f"API Error Details: {error_title} - Indicator: {indicator}")
|
|
195
|
+
|
|
196
|
+
# 处理exc字段(V1 API格式)
|
|
197
|
+
exc = response_data.get("exc", None)
|
|
198
|
+
if exc:
|
|
199
|
+
logger.error(f"API Exception: {exc}")
|
|
200
|
+
error_messages.append(str(exc))
|
|
201
|
+
error_codes.append("API_EXCEPTION")
|
|
202
|
+
|
|
203
|
+
# 处理message字段中的错误
|
|
204
|
+
messages = response_data.get("messages", None)
|
|
205
|
+
logger.error(f"API Messages: {messages}")
|
|
206
|
+
if messages and isinstance(messages, list):
|
|
207
|
+
for msg in messages:
|
|
208
|
+
if isinstance(msg, dict):
|
|
209
|
+
error_msg = msg.get("message", "Unknown error")
|
|
210
|
+
# error_title = msg.get("title", "UnknownError")
|
|
211
|
+
error_messages.append(error_msg)
|
|
212
|
+
# error_codes.append(error_title)
|
|
213
|
+
if messages and isinstance(messages, str):
|
|
214
|
+
# 检查是否是错误消息
|
|
215
|
+
if any(keyword in messages.lower() for keyword in ["error", "failed", "invalid", "unauthorized"]):
|
|
216
|
+
logger.error(f"API Error Message: {messages}")
|
|
217
|
+
error_messages.append(str(messages))
|
|
218
|
+
error_codes.append("API_ERROR")
|
|
219
|
+
|
|
220
|
+
# 如果没有找到任何错误,返回默认错误
|
|
221
|
+
if not error_messages:
|
|
222
|
+
error_messages.append("Unknown error occurred")
|
|
223
|
+
error_codes.append("UnknownError")
|
|
224
|
+
|
|
225
|
+
return {"messages": error_messages, "codes": error_codes}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def is_auth_error(error_type: str) -> bool:
|
|
229
|
+
"""
|
|
230
|
+
判断是否为认证相关错误
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
error_type: 错误类型
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
bool: 是否为认证错误
|
|
237
|
+
"""
|
|
238
|
+
auth_types = ["AuthenticationError", "PermissionError", "Unauthorized", "Forbidden"]
|
|
239
|
+
return any(auth_type in error_type for auth_type in auth_types)
|
|
240
|
+
|
|
241
|
+
|
|
@@ -1,198 +0,0 @@
|
|
|
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
|
-
status_code=response.status_code,
|
|
56
|
-
error=f"Invalid JSON response: {e}",
|
|
57
|
-
error_code="API_INVALID_JSON"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
try:
|
|
61
|
-
response_data = response.json()
|
|
62
|
-
except Exception as e:
|
|
63
|
-
return ApiResponse(
|
|
64
|
-
http_success=True,
|
|
65
|
-
api_success=False,
|
|
66
|
-
status_code=response.status_code,
|
|
67
|
-
error=f"Invalid JSON response: {e}",
|
|
68
|
-
error_code="API_INVALID_JSON"
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
# 检查API业务是否成功
|
|
72
|
-
api_success = _is_api_success(response_data)
|
|
73
|
-
|
|
74
|
-
if api_success:
|
|
75
|
-
# 成功响应,提取数据
|
|
76
|
-
data = _extract_success_data(response_data)
|
|
77
|
-
return ApiResponse(
|
|
78
|
-
http_success=http_success,
|
|
79
|
-
api_success=True,
|
|
80
|
-
status_code=response.status_code,
|
|
81
|
-
error=None,
|
|
82
|
-
error_code=None,
|
|
83
|
-
data=data
|
|
84
|
-
)
|
|
85
|
-
else:
|
|
86
|
-
# 失败响应,提取错误信息
|
|
87
|
-
error_msg, error_type = _extract_error_info(response_data)
|
|
88
|
-
return ApiResponse(
|
|
89
|
-
http_success=http_success,
|
|
90
|
-
api_success=False,
|
|
91
|
-
status_code=response.status_code,
|
|
92
|
-
error=error_msg,
|
|
93
|
-
error_code=error_type,
|
|
94
|
-
data=None
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def _is_api_success(response_data: Dict[str, Any]) -> bool:
|
|
99
|
-
"""
|
|
100
|
-
判断API业务是否成功
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
response_data: API响应数据
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
bool: 是否成功
|
|
107
|
-
"""
|
|
108
|
-
# 检查是否有错误
|
|
109
|
-
errors = response_data.get("errors", None)
|
|
110
|
-
if errors:
|
|
111
|
-
return False
|
|
112
|
-
|
|
113
|
-
# 检查是否有异常
|
|
114
|
-
exc = response_data.get("exc", None)
|
|
115
|
-
if exc:
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
# 检查HTTP状态码
|
|
119
|
-
http_status_code = response_data.get("http_status_code", None)
|
|
120
|
-
if http_status_code is not None:
|
|
121
|
-
return 200 <= http_status_code < 300
|
|
122
|
-
|
|
123
|
-
# 默认认为有message或data字段就是成功
|
|
124
|
-
return response_data.get("message", None) is not None or response_data.get("data", None) is not None
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _extract_success_data(response_data: Dict[str, Any]) -> Any:
|
|
128
|
-
"""
|
|
129
|
-
从成功响应中提取数据
|
|
130
|
-
|
|
131
|
-
Args:
|
|
132
|
-
response_data: API响应数据
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
Any: 提取的数据
|
|
136
|
-
"""
|
|
137
|
-
return response_data.get("data", None)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _extract_error_info(response_data: Dict[str, Any]) -> tuple[str, str]:
|
|
141
|
-
"""
|
|
142
|
-
从响应中提取错误信息
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
response_data: API响应数据
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
tuple[str, str]: (错误消息, 错误类型)
|
|
149
|
-
"""
|
|
150
|
-
# 处理errors字段
|
|
151
|
-
errors = response_data.get("errors", None)
|
|
152
|
-
if errors and isinstance(errors, list):
|
|
153
|
-
if errors:
|
|
154
|
-
# 获取所有错误消息,用换行符分割
|
|
155
|
-
error_messages = []
|
|
156
|
-
error_codes = []
|
|
157
|
-
for error in errors:
|
|
158
|
-
if isinstance(error, dict):
|
|
159
|
-
error_msg = error.get("message", "Unknown error")
|
|
160
|
-
error_code = error.get("type", "UnknownError")
|
|
161
|
-
error_messages.append(error_msg)
|
|
162
|
-
error_codes.append(error_code)
|
|
163
|
-
|
|
164
|
-
# 用换行符连接所有错误消息
|
|
165
|
-
combined_error_msg = "\n".join(error_messages)
|
|
166
|
-
# 使用第一个错误码作为主要错误码
|
|
167
|
-
primary_error_code = error_codes[0] if error_codes else "UnknownError"
|
|
168
|
-
return combined_error_msg, primary_error_code
|
|
169
|
-
|
|
170
|
-
# 处理exc字段(V1 API格式)
|
|
171
|
-
exc = response_data.get("exc", None)
|
|
172
|
-
if exc:
|
|
173
|
-
return str(exc), "API_EXCEPTION"
|
|
174
|
-
|
|
175
|
-
# 处理message字段中的错误
|
|
176
|
-
message = response_data.get("message", None)
|
|
177
|
-
if message and isinstance(message, str):
|
|
178
|
-
# 检查是否是错误消息
|
|
179
|
-
if any(keyword in message.lower() for keyword in ["error", "failed", "invalid", "unauthorized"]):
|
|
180
|
-
return message, "API_ERROR"
|
|
181
|
-
|
|
182
|
-
return "Unknown error occurred", "UnknownError"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def is_auth_error(error_type: str) -> bool:
|
|
186
|
-
"""
|
|
187
|
-
判断是否为认证相关错误
|
|
188
|
-
|
|
189
|
-
Args:
|
|
190
|
-
error_type: 错误类型
|
|
191
|
-
|
|
192
|
-
Returns:
|
|
193
|
-
bool: 是否为认证错误
|
|
194
|
-
"""
|
|
195
|
-
auth_types = ["AuthenticationError", "PermissionError", "Unauthorized", "Forbidden"]
|
|
196
|
-
return any(auth_type in error_type for auth_type in auth_types)
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|