fiuai-sdk-python 0.4.0__tar.gz → 0.4.2__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 (30) hide show
  1. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/PKG-INFO +1 -1
  2. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/pyproject.toml +1 -1
  3. fiuai_sdk_python-0.4.2/src/fiuai_sdk_python/__init__.py +24 -0
  4. fiuai_sdk_python-0.4.2/src/fiuai_sdk_python/client.py +387 -0
  5. fiuai_sdk_python-0.4.2/src/fiuai_sdk_python/context.py +288 -0
  6. fiuai_sdk_python-0.4.0/src/fiuai_sdk_python/__init__.py +0 -16
  7. fiuai_sdk_python-0.4.0/src/fiuai_sdk_python/auth/test_auth.py +0 -273
  8. fiuai_sdk_python-0.4.0/src/fiuai_sdk_python/client.py +0 -280
  9. fiuai_sdk_python-0.4.0/src/fiuai_sdk_python/context.py +0 -236
  10. fiuai_sdk_python-0.4.0/src/fiuai_sdk_python/test_all.py +0 -73
  11. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/.gitignore +0 -0
  12. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/CHANGELOG.md +0 -0
  13. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/LICENSE +0 -0
  14. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/README.md +0 -0
  15. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/auth/__init__.py +0 -0
  16. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/auth/header.py +0 -0
  17. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/auth/helper.py +0 -0
  18. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/auth/type.py +0 -0
  19. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/bank.py +0 -0
  20. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/company.py +0 -0
  21. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/const.py +0 -0
  22. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/error.py +0 -0
  23. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/examples/fastapi_integration.py +0 -0
  24. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/item.py +0 -0
  25. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/perm.py +0 -0
  26. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/profile.py +0 -0
  27. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/resp.py +0 -0
  28. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/setup.py +0 -0
  29. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/src/fiuai_sdk_python/type.py +0 -0
  30. {fiuai_sdk_python-0.4.0 → fiuai_sdk_python-0.4.2}/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.0
3
+ Version: 0.4.2
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.4.0"
3
+ version = "0.4.2"
4
4
  description = "FiuAI Python SDK - 企业级AI服务集成开发工具包"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,24 @@
1
+ from .client import FiuaiSDK, get_client
2
+ from .util import init_fiuai
3
+ from .profile import UserProfileInfo
4
+ from .type import UserProfile
5
+ from .context import (
6
+ RequestContext,
7
+ get_current_headers,
8
+ validate_current_context,
9
+ get_current_missing_fields,
10
+ is_current_context_valid
11
+ )
12
+
13
+ __all__ = [
14
+ 'FiuaiSDK',
15
+ 'init_fiuai',
16
+ 'get_client',
17
+ 'UserProfileInfo',
18
+ 'UserProfile',
19
+ 'RequestContext',
20
+ 'get_current_headers',
21
+ 'validate_current_context',
22
+ 'get_current_missing_fields',
23
+ 'is_current_context_valid'
24
+ ]
@@ -0,0 +1,387 @@
1
+ import httpx
2
+ from tenacity import retry, stop_after_attempt, wait_exponential
3
+ import json
4
+ from urllib.parse import quote
5
+ from typing import Any, Literal
6
+
7
+ from .util import get_client_config, is_initialized
8
+ from .error import FiuaiGeneralError, FiuaiAuthError
9
+ from logging import getLogger
10
+ from .type import UserProfile
11
+ from .profile import UserProfileInfo
12
+ from .auth import AuthHeader
13
+ from .resp import parse_response, ApiResponse
14
+ from .context import get_current_headers
15
+ from typing import Dict, Any, Optional
16
+
17
+
18
+ logger = getLogger(__name__)
19
+
20
+ class NotUploadableException(FiuaiGeneralError):
21
+ def __init__(self, doctype):
22
+ self.message = "The doctype `{1}` is not uploadable, so you can't download the template".format(doctype)
23
+
24
+
25
+
26
+
27
+ class FiuaiSDK(object):
28
+ def __init__(self,
29
+ url: str,
30
+ username: str = None,
31
+ auth_tenant_id: str = None,
32
+ current_company: str = None,
33
+ company_unique_no: str = None,
34
+ # auth_type: Literal["internal", "password"]="password",
35
+ max_api_retry: int=3,
36
+ timeout: int=5,
37
+ verify: bool=False
38
+ ):
39
+ self.username = username
40
+ self.auth_tenant_id = auth_tenant_id
41
+ self.current_company = current_company
42
+ self.company_unique_no = company_unique_no
43
+ self.verify = verify
44
+ self.url = url
45
+ self.max_api_retry = max_api_retry
46
+
47
+ self.client = httpx.Client(
48
+ verify=self.verify,
49
+ timeout=timeout,
50
+ follow_redirects=True,
51
+ proxy=None
52
+ )
53
+
54
+ # 默认的 AuthHeader,优先使用传入的参数,否则从上下文获取
55
+ self.headers = AuthHeader(
56
+ x_fiuai_user=username or "",
57
+ x_fiuai_auth_tenant_id=auth_tenant_id or "",
58
+ x_fiuai_current_company=current_company or "",
59
+ x_fiuai_impersonation="",
60
+ x_fiuai_unique_no=company_unique_no or current_company or "", # 优先使用 company_unique_no
61
+ x_fiuai_trace_id="",
62
+ )
63
+
64
+ # 临时 headers 存储,用于 set_temp_header 方法
65
+ self._temp_headers = {}
66
+
67
+ def _get_merged_headers(self, extra_headers: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
68
+ """
69
+ 获取合并后的请求头,自动注入 AuthData 中除了 sid 以外的所有字段
70
+
71
+ Args:
72
+ extra_headers: 额外的请求头,会覆盖默认值
73
+
74
+ Returns:
75
+ Dict[str, str]: 合并后的请求头字典
76
+ """
77
+ # 获取当前上下文的 headers
78
+ context_headers = get_current_headers()
79
+
80
+ # 从上下文构建 AuthData 相关的 headers
81
+ auth_headers = {}
82
+ if context_headers:
83
+ # 映射 AuthData 字段到 HTTP headers,优先使用初始化参数
84
+ current_company_value = self.current_company or context_headers.get("x-fiuai-current-company", self.headers.x_fiuai_current_company)
85
+ company_unique_no_value = self.company_unique_no or context_headers.get("x-fiuai-unique-no", self.headers.x_fiuai_unique_no)
86
+ # 如果 company_unique_no 没有值,使用 current_company 的值
87
+ if not company_unique_no_value:
88
+ company_unique_no_value = current_company_value
89
+
90
+ auth_headers.update({
91
+ "x-fiuai-user": self.username or context_headers.get("x-fiuai-user", self.headers.x_fiuai_user),
92
+ "x-fiuai-auth-tenant-id": self.auth_tenant_id or context_headers.get("x-fiuai-auth-tenant-id", self.headers.x_fiuai_auth_tenant_id),
93
+ "x-fiuai-current-company": current_company_value,
94
+ "x-fiuai-impersonation": context_headers.get("x-fiuai-impersonation", self.headers.x_fiuai_impersonation),
95
+ "x-fiuai-unique-no": company_unique_no_value,
96
+ "x-fiuai-trace-id": context_headers.get("x-fiuai-trace-id", self.headers.x_fiuai_trace_id),
97
+ "x-fiuai-lang": context_headers.get("x-fiuai-lang", self.headers.x_fiuai_lang),
98
+ "accept-language": context_headers.get("accept-language", self.headers.accept_language),
99
+ })
100
+ else:
101
+ # 如果没有上下文,使用初始化参数或默认的 headers
102
+ current_company_value = self.current_company or self.headers.x_fiuai_current_company
103
+ company_unique_no_value = self.company_unique_no or self.headers.x_fiuai_unique_no
104
+ # 如果 company_unique_no 没有值,使用 current_company 的值
105
+ if not company_unique_no_value:
106
+ company_unique_no_value = current_company_value
107
+
108
+ auth_headers = {
109
+ "x-fiuai-user": self.username or self.headers.x_fiuai_user,
110
+ "x-fiuai-auth-tenant-id": self.auth_tenant_id or self.headers.x_fiuai_auth_tenant_id,
111
+ "x-fiuai-current-company": current_company_value,
112
+ "x-fiuai-impersonation": self.headers.x_fiuai_impersonation,
113
+ "x-fiuai-unique-no": company_unique_no_value,
114
+ "x-fiuai-trace-id": self.headers.x_fiuai_trace_id,
115
+ "x-fiuai-lang": self.headers.x_fiuai_lang,
116
+ "accept-language": self.headers.accept_language,
117
+ }
118
+
119
+ # 应用临时 headers(优先级高于上下文,低于 extra_headers)
120
+ if self._temp_headers:
121
+ auth_headers.update(self._temp_headers)
122
+
123
+ # 合并 extra_headers(优先级最高)
124
+ if extra_headers:
125
+ auth_headers.update(extra_headers)
126
+
127
+ return auth_headers
128
+
129
+ def set_temp_header(self,
130
+ auth_tenant_id: Optional[str] = None,
131
+ auth_company_id: Optional[str] = None,
132
+ user_id: Optional[str] = None,
133
+ company_unique_no: Optional[str] = None) -> None:
134
+ """
135
+ 设置临时请求头,这些值会在本地请求中替换对应的header,但不覆盖context中的值
136
+
137
+ Args:
138
+ auth_tenant_id: 临时租户ID
139
+ auth_company_id: 临时公司ID(会自动设置 company_unique_no = auth_company_id)
140
+ user_id: 临时用户ID
141
+ company_unique_no: 临时公司唯一编号
142
+ """
143
+ if auth_tenant_id is not None:
144
+ self._temp_headers["x-fiuai-auth-tenant-id"] = auth_tenant_id
145
+
146
+ if auth_company_id is not None:
147
+ self._temp_headers["x-fiuai-current-company"] = auth_company_id
148
+ # 实现 company_unique_no = auth_company_id
149
+ self._temp_headers["x-fiuai-unique-no"] = auth_company_id
150
+
151
+ if user_id is not None:
152
+ self._temp_headers["x-fiuai-user"] = user_id
153
+
154
+ if company_unique_no is not None:
155
+ if auth_company_id:
156
+ self._temp_headers["x-fiuai-unique-no"] = auth_company_id
157
+ else:
158
+ self._temp_headers["x-fiuai-unique-no"] = company_unique_no
159
+
160
+ def clear_temp_headers(self) -> None:
161
+ """清除所有临时请求头"""
162
+ self._temp_headers.clear()
163
+
164
+
165
+ # def _login(self, username: str, password: str):
166
+ # r = self.client.post(self.url, data={
167
+ # 'cmd': 'login',
168
+ # 'usr': username,
169
+ # 'pwd': password
170
+ # }, headers=self.headers)
171
+
172
+ # if r.json().get('message') == "Logged In":
173
+ # self.can_download = []
174
+ # logger.info(f"Login to {self.url} success")
175
+
176
+ # ### 获取cookie
177
+ # self.headers["Fiuai-Internal-Company"] = r.cookies.get("current_company")
178
+ # self.headers["Fiuai-Internal-Tenant"] = r.cookies.get("tenant")
179
+ # return r.json()
180
+ # else:
181
+ # raise FiuaiAuthError(f"Login failed: {r.json().get('message')}")
182
+
183
+ # def _logout(self):
184
+ # logger.info(f"Logout from {self.url}")
185
+ # if self.auth_type == "password":
186
+ # # internal login 不需要logout
187
+ # self.client.get(self.url, params={"cmd": "logout"}, headers=self.headers)
188
+
189
+
190
+ def __enter__(self):
191
+ return self
192
+
193
+ def __exit__(self, *args, **kwargs):
194
+ # self._logout()
195
+ self.client.close()
196
+ # self.logout()
197
+
198
+
199
+ def get_avaliable_company(self, page: int=1, page_size: int=20, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
200
+ headers = self._get_merged_headers(extra_headers)
201
+ r = self.client.get(self.url + "/api/method/fiuai.network.doctype.company.company.get_available_companies",
202
+ params={"page": page, "page_size": page_size},
203
+ headers=headers)
204
+
205
+ return self.post_process(r)
206
+
207
+ def swith_company(self, tenant: str = "", company: str = "") -> ApiResponse:
208
+
209
+ self.headers.x_fiuai_auth_tenant_id = tenant
210
+ self.headers.x_fiuai_current_company = company
211
+
212
+ if company == "":
213
+ raise FiuaiAuthError("Company is required when using password auth")
214
+
215
+ if tenant == "":
216
+ raise FiuaiAuthError("Tenant is required when using internal auth")
217
+
218
+ # else:
219
+ # r = self.client.post(self.url + "/api/method/frappe.sessions.change_current_company",
220
+ # data={"auth_company_id": company})
221
+ # if r.status_code != 200:
222
+ # logger.error(f"Switch company failed: {r.json().get('message')}")
223
+ # # raise FiuaiAuthError(f"Switch company failed: {r.json().get('message')}")
224
+ # return False
225
+ # else:
226
+ # return True
227
+
228
+ def get_tenant(self) -> ApiResponse:
229
+ return self.headers.x_fiuai_auth_tenant_id
230
+
231
+ def get_company(self) -> ApiResponse:
232
+ return self.headers.x_fiuai_current_company
233
+
234
+ # def get_v2_api(self, uri, params={}):
235
+
236
+ # print(f"22222, ", self.headers.model_dump())
237
+ # res = self.client.get(self.url + '/api/v2/' + uri.lstrip('/'), params=params, headers=self.headers.model_dump())
238
+ # return self.post_process(res)
239
+
240
+
241
+ def get_user_profile_info(self, user_id: str=None, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
242
+ """获取详细的用户信息"""
243
+ if not user_id:
244
+ user_id = self.headers.x_fiuai_user
245
+
246
+ headers = self._get_merged_headers(extra_headers)
247
+ res = self.client.get(self.url + f"/api/v2/internal/user/profile/{user_id}", headers=headers)
248
+
249
+ profile_response = self.post_process(res)
250
+
251
+ if profile_response.is_success():
252
+ try:
253
+ profile_response.data = UserProfileInfo.model_validate(profile_response.data)
254
+ except Exception as e:
255
+ profile_response.error = str(e)
256
+ profile_response.error_code = "PROFILE_FORMAT_ERROR"
257
+ return profile_response
258
+ return profile_response
259
+ else:
260
+ return profile_response
261
+
262
+
263
+
264
+ def internal_post_req(self, uri, postdata={}, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
265
+ headers = self._get_merged_headers(extra_headers)
266
+ res = self.client.post(self.url + '/api/v2/internal/' + uri.lstrip('/'), data=postdata, headers=headers)
267
+ return self.post_process(res)
268
+
269
+ def internal_get_req(self, uri, params={}, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
270
+ headers = self._get_merged_headers(extra_headers)
271
+ res = self.client.get(self.url + '/api/v2/internal/' + uri.lstrip('/'), params=params, headers=headers)
272
+ return self.post_process(res)
273
+
274
+ def internal_create(self, data={}, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
275
+ headers = self._get_merged_headers(extra_headers)
276
+ res = self.client.post(self.url + '/api/v2/internal/doctype/create', data={"data":json.dumps(data, ensure_ascii=False)}, headers=headers)
277
+ return self.post_process(res)
278
+
279
+ def internal_get(self, doctype, name, fields=None, filters=None, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
280
+ d = {
281
+ "doctype": doctype,
282
+ "name": name,
283
+ }
284
+ if fields:
285
+ d["fields"] = json.dumps(fields)
286
+ if filters:
287
+ d["filters"] = json.dumps(filters)
288
+ headers = self._get_merged_headers(extra_headers)
289
+ res = self.client.get(self.url + '/api/v2/internal/doctype/get', params=d, headers=headers)
290
+ return self.post_process(res)
291
+
292
+ def internal_get_list(self, doctype, filters=None, fields=None, limit_start=0, limit_page_length=20, order_by=None,
293
+ extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
294
+ d = {
295
+ "doctype": doctype,
296
+ "limit_start":limit_start,
297
+ "limit_page_length":limit_page_length,
298
+ }
299
+ if filters:
300
+ d["filters"] = json.dumps(filters)
301
+ if fields:
302
+ d["fields"] = json.dumps(fields)
303
+ if order_by:
304
+ d["order_by"] = order_by
305
+ headers = self._get_merged_headers(extra_headers)
306
+ res = self.client.get(
307
+ self.url + '/api/v2/internal/doctype/get_list',
308
+ params=d,
309
+ headers=headers)
310
+ return self.post_process(res)
311
+
312
+
313
+ def internal_update(self, data={}, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
314
+ headers = self._get_merged_headers(extra_headers)
315
+ res = self.client.post(self.url + '/api/v2/internal/doctype/update', data={"data":json.dumps(data, ensure_ascii=False)}, headers=headers)
316
+ return self.post_process(res)
317
+
318
+ def internal_delete(self, doctype, name, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
319
+ headers = self._get_merged_headers(extra_headers)
320
+ res = self.client.post(self.url + '/api/v2/internal/doctype/delete', data={"doctype": doctype, "name":name}, headers=headers)
321
+ return self.post_process(res)
322
+
323
+ def internal_submit(self, doctype, name, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
324
+ headers = self._get_merged_headers(extra_headers)
325
+ res = self.client.post(self.url + '/api/v2/internal/doctype/submit', data={"doctype": doctype, "name":name}, headers=headers)
326
+ return self.post_process(res)
327
+
328
+ def internal_cancel(self, doctype, name, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
329
+ headers = self._get_merged_headers(extra_headers)
330
+ res = self.client.post(self.url + '/api/v2/internal/doctype/cancel', data={"doctype": doctype, "name":name}, headers=headers)
331
+ return self.post_process(res)
332
+
333
+
334
+ def get_meta(self, doctype: str, extra_headers: Optional[Dict[str, Any]] = None) -> ApiResponse:
335
+ headers = self._get_merged_headers(extra_headers)
336
+ res = self.client.get(self.url + '/api/v2/internal/doctype/meta/' + doctype, headers=headers)
337
+
338
+ return self.post_process(res)
339
+
340
+
341
+ def post_process(self, response) -> ApiResponse:
342
+ """
343
+ 处理API响应,使用结构化的错误处理系统
344
+
345
+ Args:
346
+ response: httpx响应对象
347
+
348
+ Returns:
349
+ Any: 成功时返回数据,失败时抛出异常
350
+
351
+ Raises:
352
+ FiuaiGeneralError: 当API返回错误时
353
+ FiuaiAuthError: 当认证相关错误时
354
+ """
355
+ # 使用resp.py中的统一解析函数
356
+ api_response = parse_response(response)
357
+
358
+ return api_response
359
+
360
+
361
+ def get_client(username: str = None, auth_tenant_id: str = None, current_company: str = None, company_unique_no: str = None) -> FiuaiSDK:
362
+ """
363
+ 获取FiuaiSDK客户端, 需要提取调用init_fiuai()初始化
364
+
365
+ Args:
366
+ username: 用户名(如果提供,优先使用此值而不是上下文)
367
+ auth_tenant_id: 租户ID(如果提供,优先使用此值而不是上下文)
368
+ current_company: 当前公司(如果提供,优先使用此值而不是上下文)
369
+ company_unique_no: 公司唯一编号(如果提供,优先使用此值而不是上下文)
370
+ """
371
+ # 检查是否已初始化
372
+ if not is_initialized():
373
+ raise ValueError("FiuaiSDK not initialized. Please call init_fiuai() first.")
374
+
375
+ client_config = get_client_config()
376
+
377
+
378
+ return FiuaiSDK(
379
+ url=client_config.url,
380
+ username=username,
381
+ auth_tenant_id=auth_tenant_id,
382
+ current_company=current_company,
383
+ company_unique_no=company_unique_no,
384
+ max_api_retry=client_config.max_api_retry,
385
+ timeout=client_config.timeout,
386
+ verify=client_config.verify,
387
+ )