fiuai-sdk-python 0.15.0__tar.gz → 0.16.0__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 (46) hide show
  1. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/CHANGELOG.md +6 -0
  2. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/PKG-INFO +1 -1
  3. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/pyproject.toml +1 -2
  4. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/__init__.py +1 -1
  5. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/auth/__init__.py +13 -2
  6. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/auth/context_mgr.py +4 -1
  7. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/auth/header.py +45 -4
  8. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/auth/helper.py +21 -1
  9. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/auth/type.py +10 -1
  10. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/client.py +3 -1
  11. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/context.py +5 -1
  12. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/http/client.py +1 -0
  13. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/.gitignore +0 -0
  14. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/LICENSE +0 -0
  15. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/README.md +0 -0
  16. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/_resp_helpers.py +0 -0
  17. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/bank.py +0 -0
  18. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/company.py +0 -0
  19. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/config.py +0 -0
  20. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/const.py +0 -0
  21. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/datatype.py +0 -0
  22. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/doctype.py +0 -0
  23. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/error.py +0 -0
  24. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/error_code.py +0 -0
  25. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/error_parsers.py +0 -0
  26. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/examples/fastapi_integration.py +0 -0
  27. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/http/__init__.py +0 -0
  28. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/item.py +0 -0
  29. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/perm.py +0 -0
  30. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/cache/__init__.py +0 -0
  31. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/cache/cache_client.py +0 -0
  32. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/cache/circuit_breaker.py +0 -0
  33. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/cache/decorator.py +0 -0
  34. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/cache/redis_manager.py +0 -0
  35. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/cache/types.py +0 -0
  36. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/scheduler/__init__.py +0 -0
  37. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/pkg/scheduler/runner.py +0 -0
  38. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/profile.py +0 -0
  39. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/resp.py +0 -0
  40. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/setup.py +0 -0
  41. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/type.py +0 -0
  42. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/util.py +0 -0
  43. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/utils/__init__.py +0 -0
  44. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/utils/ids.py +0 -0
  45. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/utils/logger.py +0 -0
  46. {fiuai_sdk_python-0.15.0 → fiuai_sdk_python-0.16.0}/src/fiuai_sdk_python/utils/text.py +0 -0
@@ -5,6 +5,12 @@ 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.0] - 2026-07-15
9
+
10
+ ### Added
11
+ - 新增 `X-FiuAI-Roles` JSON 数组认证头及 `AuthData.roles`, 支持严格解析、上下文传播和下游 HTTP 注入。
12
+ - 新增 `get_roles()` 与 `get_effective_tenant_id()` 认证 helper。
13
+
8
14
  ## [0.13.0] - 2026-05-13
9
15
 
10
16
  ### 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.0
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.0"
4
4
  description = "FiuAI Python SDK - 企业级AI服务集成开发工具包"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -75,4 +75,3 @@ dev-dependencies = [
75
75
  "pre-commit>=3.0.0",
76
76
  ]
77
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.0"
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
@@ -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