fiuai-sdk-python 0.15.0__tar.gz → 0.16.1__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 (47) hide show
  1. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/CHANGELOG.md +12 -0
  2. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/PKG-INFO +1 -1
  3. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/pyproject.toml +1 -3
  4. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/__init__.py +1 -1
  5. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/auth/__init__.py +13 -2
  6. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/auth/context_mgr.py +4 -1
  7. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/auth/header.py +45 -4
  8. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/auth/helper.py +21 -1
  9. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/auth/type.py +10 -1
  10. fiuai_sdk_python-0.16.1/src/fiuai_sdk_python/bank.py +175 -0
  11. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/client.py +3 -1
  12. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/context.py +5 -1
  13. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/http/client.py +1 -0
  14. fiuai_sdk_python-0.15.0/src/fiuai_sdk_python/bank.py +0 -175
  15. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/.gitignore +0 -0
  16. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/LICENSE +0 -0
  17. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/README.md +0 -0
  18. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/_resp_helpers.py +0 -0
  19. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/company.py +0 -0
  20. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/config.py +0 -0
  21. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/const.py +0 -0
  22. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/datatype.py +0 -0
  23. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/doctype.py +0 -0
  24. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/error.py +0 -0
  25. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/error_code.py +0 -0
  26. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/error_parsers.py +0 -0
  27. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/examples/fastapi_integration.py +0 -0
  28. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/http/__init__.py +0 -0
  29. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/item.py +0 -0
  30. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/perm.py +0 -0
  31. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/cache/__init__.py +0 -0
  32. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/cache/cache_client.py +0 -0
  33. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/cache/circuit_breaker.py +0 -0
  34. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/cache/decorator.py +0 -0
  35. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/cache/redis_manager.py +0 -0
  36. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/cache/types.py +0 -0
  37. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/scheduler/__init__.py +0 -0
  38. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/pkg/scheduler/runner.py +0 -0
  39. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/profile.py +0 -0
  40. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/resp.py +0 -0
  41. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/setup.py +0 -0
  42. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/type.py +0 -0
  43. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/util.py +0 -0
  44. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/utils/__init__.py +0 -0
  45. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/utils/ids.py +0 -0
  46. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/utils/logger.py +0 -0
  47. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.1}/src/fiuai_sdk_python/utils/text.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.16.1] - 2026-07-18
9
+
10
+ ### Fixed
11
+ - Bank Account 已存在但未关联标准 Bank/Branch 时仍返回 `exists=True`,关联字段作为可选 enrichment。
12
+ - 修正 `CompanyBankAccountSearchResult.bank_id` 错误返回银行账号 ID。
13
+
14
+ ## [0.16.0] - 2026-07-15
15
+
16
+ ### Added
17
+ - 新增 `X-FiuAI-Roles` JSON 数组认证头及 `AuthData.roles`, 支持严格解析、上下文传播和下游 HTTP 注入。
18
+ - 新增 `get_roles()` 与 `get_effective_tenant_id()` 认证 helper。
19
+
8
20
  ## [0.13.0] - 2026-05-13
9
21
 
10
22
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiuai_sdk_python
3
- Version: 0.15.0
3
+ Version: 0.16.1
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.15.0"
3
+ version = "0.16.1"
4
4
  description = "FiuAI Python SDK - 企业级AI服务集成开发工具包"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -74,5 +74,3 @@ dev-dependencies = [
74
74
  "mypy>=1.0.0",
75
75
  "pre-commit>=3.0.0",
76
76
  ]
77
-
78
-
@@ -18,7 +18,7 @@ from .error_code import (
18
18
  )
19
19
  from .resp import ApiError, ApiResponse, parse_response
20
20
 
21
- __version__ = "0.13.0"
21
+ __version__ = "0.16.1"
22
22
 
23
23
  __all__ = [
24
24
  'FiuaiSDK',
@@ -1,4 +1,9 @@
1
- from .header import parse_auth_headers, extract_auth_from_request
1
+ from .header import (
2
+ extract_auth_from_request,
3
+ parse_auth_headers,
4
+ parse_roles_header,
5
+ serialize_roles_header,
6
+ )
2
7
  from .type import AuthData, AuthHeader
3
8
  from .helper import (
4
9
  get_auth_data,
@@ -7,6 +12,8 @@ from .helper import (
7
12
  get_current_company,
8
13
  get_company_unique_no,
9
14
  get_impersonation,
15
+ get_roles,
16
+ get_effective_tenant_id,
10
17
  is_impersonating,
11
18
  )
12
19
  from .context_mgr import (
@@ -27,6 +34,8 @@ get_auth_data_from_request = get_auth_data
27
34
  __all__ = [
28
35
  "parse_auth_headers",
29
36
  "extract_auth_from_request",
37
+ "parse_roles_header",
38
+ "serialize_roles_header",
30
39
  "AuthData",
31
40
  "AuthHeader",
32
41
  "get_auth_data",
@@ -37,6 +46,8 @@ __all__ = [
37
46
  "get_current_company",
38
47
  "get_company_unique_no",
39
48
  "get_impersonation",
49
+ "get_roles",
50
+ "get_effective_tenant_id",
40
51
  "is_impersonating",
41
52
  "ContextManager",
42
53
  "init_context",
@@ -46,4 +57,4 @@ __all__ = [
46
57
  "get_world_data",
47
58
  "set_user_profile_info",
48
59
  "get_user_profile_info",
49
- ]
60
+ ]
@@ -20,7 +20,7 @@ from pydantic import BaseModel, Field
20
20
 
21
21
  from ..context import RequestContext, get_current_headers, is_current_context_valid
22
22
  from .type import AuthData, FIUAI_CLIENT_VALUES
23
- from .header import parse_auth_headers
23
+ from .header import parse_auth_headers, serialize_roles_header
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
26
 
@@ -92,6 +92,7 @@ class ContextManager:
92
92
  "x-fiuai-channel": "default",
93
93
  "x-fiuai-lang": auth_data.lang or "zh",
94
94
  "accept-language": auth_data.accept_language or "zh",
95
+ "x-fiuai-roles": serialize_roles_header(auth_data.roles),
95
96
  }
96
97
  return RequestContext.from_dict(headers)
97
98
 
@@ -182,6 +183,7 @@ def init_context(
182
183
  lang=kwargs.get("lang", "zh"),
183
184
  accept_language=kwargs.get("accept_language", "zh"),
184
185
  model=(kwargs.get("model") or "auto").strip() or "auto",
186
+ roles=kwargs.get("roles") or [],
185
187
  )
186
188
  return ContextManager(
187
189
  auth_data=auth_data,
@@ -275,6 +277,7 @@ def update_auth_data(
275
277
  lang=kwargs.get("lang", current.lang),
276
278
  accept_language=kwargs.get("accept_language", current.accept_language),
277
279
  model=model,
280
+ roles=kwargs.get("roles", current.roles),
278
281
  )
279
282
  return set_auth_data(new_auth_data)
280
283
  except Exception as e:
@@ -5,12 +5,51 @@
5
5
  # Email: lmlala@aliyun.com
6
6
  # Copyright (c) 2025 FiuAI
7
7
 
8
+ import json
8
9
  from typing import Dict, Optional, Union, Literal
9
10
  from .type import AuthData, FIUAI_CLIENT_VALUES
10
11
  from fastapi import Request
11
12
 
12
13
 
13
- def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
14
+ def parse_roles_header(raw_value: str | None) -> list[str]:
15
+ """严格解析 ``X-FiuAI-Roles`` JSON 数组并保持首次出现顺序."""
16
+ value = (raw_value or "").strip()
17
+ if not value:
18
+ return []
19
+ try:
20
+ decoded = json.loads(value)
21
+ except json.JSONDecodeError as exc:
22
+ raise ValueError("Invalid header x-fiuai-roles: expected JSON array") from exc
23
+ if not isinstance(decoded, list):
24
+ raise ValueError("Invalid header x-fiuai-roles: expected JSON array")
25
+
26
+ roles: list[str] = []
27
+ seen: set[str] = set()
28
+ for raw_role in decoded:
29
+ if not isinstance(raw_role, str):
30
+ raise ValueError("Invalid header x-fiuai-roles: roles must be strings")
31
+ role = raw_role.strip()
32
+ if not role:
33
+ raise ValueError("Invalid header x-fiuai-roles: role cannot be empty")
34
+ if role not in seen:
35
+ seen.add(role)
36
+ roles.append(role)
37
+ return roles
38
+
39
+
40
+ def serialize_roles_header(roles: list[str] | tuple[str, ...] | None) -> str:
41
+ """把角色代码规范化为紧凑、无歧义的 JSON header 值."""
42
+ normalized = parse_roles_header(
43
+ json.dumps(list(roles or []), ensure_ascii=True, separators=(",", ":"))
44
+ )
45
+ return json.dumps(normalized, ensure_ascii=True, separators=(",", ":"))
46
+
47
+
48
+ def parse_auth_headers(
49
+ headers: Dict[str, str],
50
+ *,
51
+ allow_empty_company: bool = False,
52
+ ) -> Optional[AuthData]:
14
53
  """
15
54
  从 HTTP headers 中解析认证信息并转换为 AuthData
16
55
 
@@ -38,6 +77,7 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
38
77
  channel = "default"
39
78
  lang = headers.get("x-fiuai-lang", "zh") or "zh"
40
79
  accept_language = headers.get("accept-language", "zh") or "zh"
80
+ roles = parse_roles_header(headers.get("x-fiuai-roles"))
41
81
 
42
82
  # 验证必需字段
43
83
  if not user_id:
@@ -51,17 +91,17 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
51
91
  current_company = [company.strip() for company in current_company_str.split(",") if company.strip()]
52
92
 
53
93
  # 检查是否有有效的公司信息
54
- if not current_company:
94
+ if not current_company and not allow_empty_company:
55
95
  raise ValueError("Missing required header: x-fiuai-current-company")
56
96
 
57
97
  # 如果没有提供 unique_no,使用第一个公司ID作为默认值
58
- if not unique_no:
98
+ if not unique_no and current_company:
59
99
  unique_no = current_company[0]
60
100
 
61
101
  return AuthData(
62
102
  user_id=user_id,
63
103
  auth_tenant_id=auth_tenant_id,
64
- current_company=current_company[0],
104
+ current_company=current_company[0] if current_company else "",
65
105
  impersonation=impersonation,
66
106
  company_unique_no=unique_no,
67
107
  trace_id=trace_id,
@@ -70,6 +110,7 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
70
110
  lang=lang,
71
111
  accept_language=accept_language,
72
112
  model=model,
113
+ roles=roles,
73
114
  )
74
115
 
75
116
  except Exception as e:
@@ -103,6 +103,26 @@ def get_impersonation(
103
103
  return auth_data.impersonation if auth_data else None
104
104
 
105
105
 
106
+ def get_roles(
107
+ request: Optional[Union[Request, Dict[str, str]]] = None,
108
+ engine: Literal["fastapi", "dict"] = "fastapi",
109
+ ) -> list[str]:
110
+ """获取当前认证角色;无认证上下文时返回空列表."""
111
+ auth_data = get_auth_data(request, engine)
112
+ return list(auth_data.roles) if auth_data else []
113
+
114
+
115
+ def get_effective_tenant_id(
116
+ request: Optional[Union[Request, Dict[str, str]]] = None,
117
+ engine: Literal["fastapi", "dict"] = "fastapi",
118
+ ) -> Optional[str]:
119
+ """获取权限判断使用的有效租户,impersonation 优先."""
120
+ auth_data = get_auth_data(request, engine)
121
+ if not auth_data:
122
+ return None
123
+ return auth_data.impersonation or auth_data.auth_tenant_id
124
+
125
+
106
126
  def get_current_client(
107
127
  request: Optional[Union[Request, Dict[str, str]]] = None,
108
128
  engine: Literal["fastapi", "dict"] = "fastapi",
@@ -137,4 +157,4 @@ def get_current_model_mode(
137
157
  获取当前模型模式。request 为 None 时从当前上下文取;无上下文或未认证时返回 None。
138
158
  """
139
159
  auth_data = get_auth_data(request, engine)
140
- return auth_data.model if auth_data else None
160
+ return auth_data.model if auth_data else None
@@ -31,6 +31,10 @@ class AuthData(BaseModel):
31
31
  description="下游/本地自定义 LLM 模型标识, 请求头 x-fiuai-model, 默认 auto",
32
32
  default="auto",
33
33
  )
34
+ roles: list[str] = Field(
35
+ description="网关认证后注入的规范化角色代码",
36
+ default_factory=list,
37
+ )
34
38
 
35
39
  # edoc
36
40
 
@@ -49,8 +53,13 @@ class AuthHeader(BaseModel):
49
53
  default="unknown",
50
54
  description="本地请求自定义 model, 默认 unknown",
51
55
  )
56
+ x_fiuai_roles: str = Field(
57
+ alias="x-fiuai-roles",
58
+ default="[]",
59
+ description="规范化角色代码 JSON 数组",
60
+ )
52
61
  x_fiuai_channel: str = Field(alias="x-fiuai-channel", default="default", description="渠道code,目前未用")
53
62
  accept_language: str = Field(alias="accept-language", default="zh", description="语言")
54
63
 
55
64
  class Config:
56
- populate_by_name = True
65
+ populate_by_name = True
@@ -0,0 +1,175 @@
1
+ # -- coding: utf-8 --
2
+ # Project: fiuaiclient
3
+ # Created Date: 2025 06 Fr
4
+ # Author: liming
5
+ # Email: lmlala@aliyun.com
6
+ # Copyright (c) 2025 FiuAI
7
+ import logging
8
+ from typing import Optional
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ from .client import FiuaiSDK
13
+ from .type import BankAccount
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CompanyBankAccountSearchResult(BaseModel):
19
+ bank_account_id: str = Field(description="银行账号id", default="")
20
+ bank_account_no: str = Field(description="银行账号", default="")
21
+ bank_account_name: str = Field(description="银行账号", default="")
22
+ bank_id: str = Field(description="银行id", default="")
23
+ bank_name: str = Field(description="银行名称", default="")
24
+ opening_bank_branch_id: str = Field(description="开户行id", default="")
25
+ opening_bank_branch_name: str = Field(description="开户行名称", default="")
26
+
27
+
28
+ exists: bool = Field(description="是否存在", default=False)
29
+
30
+ comment: str = Field(description="备注", default="")
31
+
32
+
33
+ def get_bank_account(
34
+ client: FiuaiSDK,
35
+ bank_account_no: str,
36
+ ) -> Optional[CompanyBankAccountSearchResult]:
37
+ """
38
+ 根据银行账号获取银行账号信息
39
+
40
+ Args:
41
+ bank_account_no: 银行账号
42
+
43
+ Returns:
44
+ CompanyBankAccountSearchResult: 银行账号信息
45
+ """
46
+
47
+ if not bank_account_no:
48
+ logger.error("bank_account_no is empty, return None")
49
+ return None
50
+
51
+ bank_account_response = client.internal_get(
52
+ doctype="Bank Account",
53
+ name=bank_account_no,
54
+ fields=[
55
+ "name",
56
+ "bank_account_no",
57
+ "account_name",
58
+ "bank",
59
+ "opening_bank_branch",
60
+ ],
61
+ )
62
+
63
+ if not bank_account_response.is_success():
64
+ return CompanyBankAccountSearchResult(exists=False)
65
+
66
+ bank_account = bank_account_response.data
67
+ if not bank_account:
68
+ return CompanyBankAccountSearchResult(exists=False)
69
+
70
+ bank_id = str(bank_account.get("bank") or "")
71
+ bank_name = ""
72
+ if bank_id:
73
+ bank_response = client.internal_get(
74
+ doctype="Bank",
75
+ name=bank_id,
76
+ fields=["name", "bank_name"],
77
+ )
78
+ if bank_response.is_success() and bank_response.data:
79
+ bank_name = str(bank_response.data.get("bank_name") or "")
80
+ else:
81
+ logger.warning("bank enrichment failed for id %s", bank_id)
82
+
83
+ opening_value = str(bank_account.get("opening_bank_branch") or "")
84
+ opening_bank_branch_id = ""
85
+ opening_bank_branch_name = opening_value
86
+ if bank_id and opening_value:
87
+ branch_response = client.internal_get(
88
+ doctype="Bank Branch",
89
+ name=opening_value,
90
+ fields=["name", "bank_branch_name"],
91
+ )
92
+ if branch_response.is_success() and branch_response.data:
93
+ opening_bank_branch_id = str(branch_response.data.get("name") or "")
94
+ opening_bank_branch_name = str(
95
+ branch_response.data.get("bank_branch_name") or ""
96
+ )
97
+ else:
98
+ logger.warning("bank branch enrichment failed for id %s", opening_value)
99
+
100
+ return CompanyBankAccountSearchResult(
101
+ exists=True,
102
+ bank_account_id=str(bank_account.get("name") or ""),
103
+ bank_account_no=str(bank_account.get("bank_account_no") or ""),
104
+ bank_account_name=str(bank_account.get("account_name") or ""),
105
+ bank_id=bank_id,
106
+ bank_name=bank_name,
107
+ opening_bank_branch_id=opening_bank_branch_id,
108
+ opening_bank_branch_name=opening_bank_branch_name,
109
+ )
110
+
111
+
112
+ def get_bank_account_by_account_no(
113
+ client: FiuaiSDK,
114
+ account_no: str,
115
+ ) -> BankAccount | None:
116
+ """
117
+ 根据银行账号id获取银行账号信息
118
+ """
119
+ response = client.internal_get_list(
120
+ doctype="Bank Account",
121
+ filters=[["bank_account_no", "=", account_no]],
122
+ fields=[
123
+ "name",
124
+ "account_name",
125
+ "bank",
126
+ "bank_account_no",
127
+ "opening_bank_branch",
128
+ ],
129
+ )
130
+
131
+ if not response.is_success():
132
+ logger.warning("bank account is not found by account no %s", account_no)
133
+ return None
134
+
135
+ rows = response.data
136
+ if not rows:
137
+ logger.warning("bank account is not found by account no %s", account_no)
138
+ return None
139
+
140
+ bank_account = rows[0]
141
+ bank_id = str(bank_account.get("bank") or "")
142
+ bank_name = ""
143
+ if bank_id:
144
+ bank_response = client.internal_get(
145
+ doctype="Bank",
146
+ name=bank_id,
147
+ fields=["name", "bank_name"],
148
+ )
149
+ if bank_response.is_success() and bank_response.data:
150
+ bank_name = str(bank_response.data.get("bank_name") or "")
151
+ else:
152
+ logger.warning("bank enrichment failed for id %s", bank_id)
153
+
154
+ opening_value = str(bank_account.get("opening_bank_branch") or "")
155
+ opening_bank_branch_name = opening_value
156
+ if bank_id and opening_value:
157
+ branch_response = client.internal_get(
158
+ doctype="Bank Branch",
159
+ name=opening_value,
160
+ fields=["name", "bank_branch_name"],
161
+ )
162
+ if branch_response.is_success() and branch_response.data:
163
+ opening_bank_branch_name = str(
164
+ branch_response.data.get("bank_branch_name") or ""
165
+ )
166
+ else:
167
+ logger.warning("bank branch enrichment failed for id %s", opening_value)
168
+
169
+ return BankAccount(
170
+ name=str(bank_account.get("name") or ""),
171
+ account_name=str(bank_account.get("account_name") or ""),
172
+ bank=bank_name,
173
+ bank_account_no=account_no,
174
+ bank_branch=opening_bank_branch_name,
175
+ )
@@ -69,6 +69,7 @@ class FiuaiSDK(object):
69
69
  x_fiuai_impersonation="",
70
70
  x_fiuai_unique_no=company_unique_no or current_company or "", # 优先使用 company_unique_no
71
71
  x_fiuai_trace_id="",
72
+ x_fiuai_roles="[]",
72
73
  )
73
74
 
74
75
  # 临时 headers 存储,用于 set_temp_header 方法
@@ -116,6 +117,7 @@ class FiuaiSDK(object):
116
117
  "x-fiuai-channel": context_headers.get("x-fiuai-channel", self.headers.x_fiuai_channel),
117
118
  "x-fiuai-lang": context_headers.get("x-fiuai-lang", self.headers.x_fiuai_lang),
118
119
  "accept-language": context_headers.get("accept-language", self.headers.accept_language),
120
+ "x-fiuai-roles": context_headers.get("x-fiuai-roles", self.headers.x_fiuai_roles),
119
121
  })
120
122
  else:
121
123
  # 如果没有上下文,使用初始化参数或默认的 headers
@@ -137,6 +139,7 @@ class FiuaiSDK(object):
137
139
  "x-fiuai-channel": self.headers.x_fiuai_channel,
138
140
  "x-fiuai-lang": self.headers.x_fiuai_lang,
139
141
  "accept-language": self.headers.accept_language,
142
+ "x-fiuai-roles": self.headers.x_fiuai_roles,
140
143
  }
141
144
 
142
145
  # 应用临时 headers(优先级高于上下文,低于 extra_headers)
@@ -588,4 +591,3 @@ def get_client(username: str = None, auth_tenant_id: str = None, current_company
588
591
  host=client_config.host,
589
592
  trust_env=client_config.trust_env,
590
593
  )
591
-
@@ -105,6 +105,8 @@ class RequestContext:
105
105
  processed_headers["x-fiuai-client"] = "default"
106
106
  if not (processed_headers.get("x-fiuai-model") or "").strip():
107
107
  processed_headers["x-fiuai-model"] = "auto"
108
+ if not (processed_headers.get("x-fiuai-roles") or "").strip():
109
+ processed_headers["x-fiuai-roles"] = "[]"
108
110
 
109
111
  return processed_headers
110
112
 
@@ -250,6 +252,7 @@ def extract_auth_headers_from_context(
250
252
  channel: str = "default",
251
253
  lang: str = "zh",
252
254
  accept_language: str = "zh",
255
+ roles: str = "[]",
253
256
  ) -> AuthHeader:
254
257
  """
255
258
  从当前上下文中提取认证头信息,并合并用户提供的参数
@@ -287,6 +290,7 @@ def extract_auth_headers_from_context(
287
290
  x_fiuai_channel="default",
288
291
  x_fiuai_lang=lang,
289
292
  accept_language=accept_language,
293
+ x_fiuai_roles=roles or "[]",
290
294
  )
291
295
 
292
296
  # 从上下文中提取信息,优先使用上下文中的值
@@ -310,5 +314,5 @@ def extract_auth_headers_from_context(
310
314
  x_fiuai_channel="default",
311
315
  x_fiuai_lang=context_lang or lang,
312
316
  accept_language=context_accept_language or accept_language,
317
+ x_fiuai_roles=context_headers.get("x-fiuai-roles") or roles or "[]",
313
318
  )
314
-
@@ -33,6 +33,7 @@ AUTH_HEADER_KEYS = [
33
33
  "x-fiuai-channel",
34
34
  "x-fiuai-lang",
35
35
  "accept-language",
36
+ "x-fiuai-roles",
36
37
  ]
37
38
 
38
39
 
@@ -1,175 +0,0 @@
1
- # -- coding: utf-8 --
2
- # Project: fiuaiclient
3
- # Created Date: 2025 06 Fr
4
- # Author: liming
5
- # Email: lmlala@aliyun.com
6
- # Copyright (c) 2025 FiuAI
7
-
8
-
9
-
10
- from pydantic import BaseModel, Field
11
- import logging
12
- from .type import BankAccount
13
- from .client import FiuaiSDK
14
- from typing import Literal, Optional
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- class CompanyBankAccountSearchResult(BaseModel):
20
- bank_account_id: str = Field(description="银行账号id", default="")
21
- bank_account_no: str = Field(description="银行账号", default="")
22
- bank_account_name: str = Field(description="银行账号", default="")
23
- bank_id: str = Field(description="银行id", default="")
24
- bank_name: str = Field(description="银行名称", default="")
25
- opening_bank_branch_id: str = Field(description="开户行id", default="")
26
- opening_bank_branch_name: str = Field(description="开户行名称", default="")
27
-
28
-
29
- exists: bool = Field(description="是否存在", default=False)
30
-
31
- comment: str = Field(description="备注", default="")
32
-
33
-
34
- def get_bank_account(client: FiuaiSDK, bank_account_no: str) -> Optional[CompanyBankAccountSearchResult]:
35
- """
36
- 根据银行账号获取银行账号信息
37
-
38
- Args:
39
- back_account: 银行账号
40
-
41
- Returns:
42
- CompanyBankAccountSearchResult: 银行账号信息
43
- """
44
-
45
- if not bank_account_no:
46
- logger.error(f"bank_account_no is empty, return None")
47
- return None
48
-
49
- _bank_account_response = client.internal_get(
50
- doctype="Bank Account",
51
- name=bank_account_no,
52
- fields=["name","bank_account_no", "account_name", "bank", "opening_bank_branch"]
53
- )
54
-
55
-
56
- if not _bank_account_response.is_success():
57
- return CompanyBankAccountSearchResult(exists=False)
58
-
59
- _bank_account = _bank_account_response.data
60
- if not _bank_account:
61
- return CompanyBankAccountSearchResult(exists=False)
62
-
63
-
64
- # 获取银行信息
65
- _bank_detail_response = client.internal_get(
66
- doctype="Bank",
67
- name=_bank_account["bank"],
68
- fields=["name", "bank_name"]
69
- )
70
- if not _bank_detail_response.is_success():
71
- return CompanyBankAccountSearchResult(exists=False)
72
-
73
- _bank_detail = _bank_detail_response.data
74
- if not _bank_detail:
75
- logger.error(f"bank is not found by id {_bank_account["bank"]}")
76
- return CompanyBankAccountSearchResult(exists=False)
77
-
78
- # 获取支行信息
79
- _bank_branch_response = client.internal_get(
80
- doctype="Bank Branch",
81
- name=_bank_account["opening_bank_branch"],
82
- fields=["name", "bank_branch_name"]
83
- )
84
- if not _bank_branch_response.is_success():
85
- return CompanyBankAccountSearchResult(exists=False)
86
-
87
- _bank_branch = _bank_branch_response.data
88
- if not _bank_branch:
89
- logger.error(f"bank branch is not found by id {_bank_account["opening_bank_branch"]}")
90
- return CompanyBankAccountSearchResult(exists=False)
91
-
92
- return CompanyBankAccountSearchResult(
93
- exists=True,
94
- bank_account_id=_bank_account["name"],
95
- bank_account_no=_bank_account["bank_account_no"],
96
- bank_account_name=_bank_account["account_name"],
97
- bank_id=_bank_account["name"],
98
- bank_name=_bank_detail["bank_name"],
99
- opening_bank_branch_id=_bank_branch["name"],
100
- opening_bank_branch_name=_bank_branch["bank_branch_name"]
101
- )
102
-
103
-
104
- def get_bank_account_by_account_no(client: FiuaiSDK, account_no: str) -> BankAccount|None:
105
- """
106
- 根据银行账号id获取银行账号信息
107
- """
108
- resp_response = client.internal_get_list(
109
- doctype="Bank Account",
110
- filters=[["bank_account_no", "=", account_no]],
111
- fields=[
112
- "name",
113
- "account_name",
114
- "bank",
115
- "bank_account_no",
116
- "opening_bank_branch",
117
- ],
118
- )
119
-
120
- if not resp_response.is_success():
121
- logger.warning(f"bank account is not found by account no {account_no}")
122
- return None
123
-
124
- resp = resp_response.data
125
- if not resp:
126
- logger.warning(f"bank account is not found by account no {account_no}")
127
- return None
128
-
129
- resp = resp[0]
130
-
131
- # 获取支行信息
132
- if resp["opening_bank_branch"] != "":
133
- _bank_branch_response = client.internal_get_list(
134
- doctype="Bank Branch",
135
- filters=[["bank_branch_name", "=", resp["opening_bank_branch"]]],
136
- fields=["parent_bank", "bank_branch_name"]
137
- )
138
- if not _bank_branch_response.is_success():
139
- logger.warning(f"bank branch is not found by id {resp["opening_bank_branch"]}")
140
- return None
141
-
142
- if not _bank_branch_response.data:
143
- logger.warning(f"bank branch is not found by id {resp["opening_bank_branch"]}")
144
- return None
145
-
146
-
147
- _parent_bank_id = _bank_branch_response.data[0]["parent_bank"]
148
- if not _parent_bank_id:
149
- logger.warning(f"parent bank is not found by id {resp["opening_bank_branch"]}")
150
- return None
151
-
152
-
153
- # 获取银行信息
154
- _bank_detail_response = client.internal_get(
155
- doctype="Bank",
156
- name=_parent_bank_id,
157
- fields=["name", "bank_name"]
158
- )
159
-
160
- if not _bank_detail_response.is_success():
161
- logger.warning(f"bank is not found by id {_parent_bank_id}")
162
- return None
163
-
164
- _bank_detail = _bank_detail_response.data
165
- if not _bank_detail:
166
- logger.warning(f"bank is not found by id {_parent_bank_id}")
167
- return None
168
-
169
- return BankAccount(
170
- name=resp["name"],
171
- account_name=resp["account_name"],
172
- bank=_bank_detail["bank_name"],
173
- bank_account_no=account_no,
174
- bank_branch=resp["opening_bank_branch"],
175
- )