tamar-file-hub-client 0.1.6__tar.gz → 0.1.7__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.
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/PKG-INFO +1 -1
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/context.py +171 -171
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/file/async_blob_service.py +25 -8
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/file/sync_blob_service.py +25 -8
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/setup.py +1 -1
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/tamar_file_hub_client.egg-info/PKG-INFO +1 -1
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/MANIFEST.in +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/README.md +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/client.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/export_format.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/role.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/upload_mode.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/errors/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/errors/exceptions.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/py.typed +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/async_client.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/file_service_pb2.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/file_service_pb2_grpc.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/folder_service_pb2.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/folder_service_pb2_grpc.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/taple_service_pb2.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/taple_service_pb2_grpc.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/interceptors.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/protos/file_service.proto +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/protos/folder_service.proto +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/protos/taple_service.proto +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/sync_client.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/file.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/folder.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/taple.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/file/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/file/async_file_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/file/base_file_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/file/sync_file_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/folder/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/folder/async_folder_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/folder/sync_folder_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/taple/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/taple/async_taple_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/taple/base_taple_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/taple/idempotent_taple_mixin.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/taple/sync_taple_service.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/__init__.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/converter.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/download_helper.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/file_utils.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/idempotency.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/ip_detector.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/logging.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/mime_extension_mapper.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/retry.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/smart_retry.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/upload_helper.py +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/setup.cfg +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/tamar_file_hub_client.egg-info/SOURCES.txt +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/tamar_file_hub_client.egg-info/dependency_links.txt +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/tamar_file_hub_client.egg-info/requires.txt +0 -0
- {tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/tamar_file_hub_client.egg-info/top_level.txt +0 -0
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/context.py
RENAMED
@@ -1,171 +1,171 @@
|
|
1
|
-
"""
|
2
|
-
用户上下文和请求上下文相关的Schema定义
|
3
|
-
"""
|
4
|
-
from dataclasses import dataclass, field
|
5
|
-
from typing import Optional, Dict, Any
|
6
|
-
from datetime import datetime
|
7
|
-
|
8
|
-
from file_hub_client.enums import Role
|
9
|
-
|
10
|
-
|
11
|
-
@dataclass
|
12
|
-
class UserContext:
|
13
|
-
"""
|
14
|
-
用户上下文信息
|
15
|
-
|
16
|
-
包含两大类信息:
|
17
|
-
1. ownership(所有权): org_id, user_id - 表示资源归属
|
18
|
-
2. operator(操作者): actor_id, role - 表示实际操作者(可能是用户、agent或系统)
|
19
|
-
3. request info(请求信息): user_ip - 表示请求来源IP(用于审计和安全)
|
20
|
-
"""
|
21
|
-
# Ownership - 资源所有权信息
|
22
|
-
org_id: str # 组织ID
|
23
|
-
user_id: str # 用户ID
|
24
|
-
|
25
|
-
# Operator - 操作者信息
|
26
|
-
actor_id: Optional[str] = None # 实际操作者ID(如果为空,默认使用user_id)
|
27
|
-
role: Role = Role.ACCOUNT # 操作者角色(ACCOUNT, AGENT, SYSTEM等)
|
28
|
-
|
29
|
-
# Request info - 请求信息
|
30
|
-
user_ip: Optional[str] = None # 用户IP地址(请求来源IP)
|
31
|
-
|
32
|
-
def __post_init__(self):
|
33
|
-
"""初始化后处理,如果actor_id为空,默认使用user_id"""
|
34
|
-
if self.actor_id is None:
|
35
|
-
self.actor_id = self.user_id
|
36
|
-
|
37
|
-
def to_metadata(self) -> Dict[str, str]:
|
38
|
-
"""转换为gRPC metadata格式"""
|
39
|
-
metadata = {
|
40
|
-
'x-org-id': self.org_id,
|
41
|
-
'x-user-id': self.user_id,
|
42
|
-
'x-actor-id': self.actor_id,
|
43
|
-
'x-role': self.role,
|
44
|
-
}
|
45
|
-
|
46
|
-
# 只有当user_ip不为None时才添加x-user-ip
|
47
|
-
if self.user_ip is not None:
|
48
|
-
metadata['x-user-ip'] = self.user_ip
|
49
|
-
|
50
|
-
return metadata
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def from_metadata(cls, metadata: Dict[str, str]) -> Optional['UserContext']:
|
54
|
-
"""从metadata中解析用户上下文"""
|
55
|
-
org_id = metadata.get('x-org-id')
|
56
|
-
user_id = metadata.get('x-user-id')
|
57
|
-
|
58
|
-
if not org_id or not user_id:
|
59
|
-
return None
|
60
|
-
|
61
|
-
return cls(
|
62
|
-
org_id=org_id,
|
63
|
-
user_id=user_id,
|
64
|
-
actor_id=metadata.get('x-actor-id'),
|
65
|
-
role=Role(metadata.get('x-role', Role.ACCOUNT)),
|
66
|
-
user_ip=metadata.get('x-user-ip')
|
67
|
-
)
|
68
|
-
|
69
|
-
|
70
|
-
@dataclass
|
71
|
-
class RequestContext:
|
72
|
-
"""
|
73
|
-
请求上下文信息
|
74
|
-
|
75
|
-
包含请求相关的元数据,如客户端信息、请求追踪等
|
76
|
-
"""
|
77
|
-
request_id: Optional[str] = None # 请求ID,用于追踪
|
78
|
-
client_ip: Optional[str] = None # 客户端IP地址
|
79
|
-
client_version: Optional[str] = None # 客户端版本
|
80
|
-
client_type: Optional[str] = None # 客户端类型(web, mobile, desktop, cli等)
|
81
|
-
user_agent: Optional[str] = None # User-Agent信息
|
82
|
-
timestamp: Optional[datetime] = field(default_factory=datetime.now) # 请求时间戳
|
83
|
-
extra: Dict[str, Any] = field(default_factory=dict) # 其他扩展信息
|
84
|
-
|
85
|
-
def to_metadata(self) -> Dict[str, str]:
|
86
|
-
"""转换为gRPC metadata格式"""
|
87
|
-
metadata = {}
|
88
|
-
|
89
|
-
if self.request_id:
|
90
|
-
metadata['x-request-id'] = self.request_id
|
91
|
-
if self.client_ip:
|
92
|
-
metadata['x-client-ip'] = self.client_ip
|
93
|
-
if self.client_version:
|
94
|
-
metadata['x-client-version'] = self.client_version
|
95
|
-
if self.client_type:
|
96
|
-
metadata['x-client-type'] = self.client_type
|
97
|
-
if self.user_agent:
|
98
|
-
metadata['x-user-agent'] = self.user_agent
|
99
|
-
if self.timestamp:
|
100
|
-
metadata['x-timestamp'] = self.timestamp.isoformat()
|
101
|
-
|
102
|
-
# 添加扩展信息
|
103
|
-
for key, value in self.extra.items():
|
104
|
-
metadata[f'x-{key}'] = str(value)
|
105
|
-
|
106
|
-
return metadata
|
107
|
-
|
108
|
-
@classmethod
|
109
|
-
def from_metadata(cls, metadata: Dict[str, str]) -> 'RequestContext':
|
110
|
-
"""从metadata中解析请求上下文"""
|
111
|
-
# 提取标准字段
|
112
|
-
request_id = metadata.get('x-request-id')
|
113
|
-
client_ip = metadata.get('x-client-ip')
|
114
|
-
client_version = metadata.get('x-client-version')
|
115
|
-
client_type = metadata.get('x-client-type')
|
116
|
-
user_agent = metadata.get('x-user-agent')
|
117
|
-
|
118
|
-
# 解析时间戳
|
119
|
-
timestamp = None
|
120
|
-
if 'x-timestamp' in metadata:
|
121
|
-
try:
|
122
|
-
timestamp = datetime.fromisoformat(metadata['x-timestamp'])
|
123
|
-
except:
|
124
|
-
pass
|
125
|
-
|
126
|
-
# 提取扩展字段
|
127
|
-
extra = {}
|
128
|
-
for key, value in metadata.items():
|
129
|
-
if key.startswith('x-') and key not in [
|
130
|
-
'x-request-id', 'x-client-ip', 'x-client-version',
|
131
|
-
'x-client-type', 'x-user-agent', 'x-timestamp',
|
132
|
-
'x-org-id', 'x-user-id', 'x-actor-id', 'x-role'
|
133
|
-
]:
|
134
|
-
extra[key[2:]] = value # 去掉 'x-' 前缀
|
135
|
-
|
136
|
-
return cls(
|
137
|
-
request_id=request_id,
|
138
|
-
client_ip=client_ip,
|
139
|
-
client_version=client_version,
|
140
|
-
client_type=client_type,
|
141
|
-
user_agent=user_agent,
|
142
|
-
timestamp=timestamp,
|
143
|
-
extra=extra
|
144
|
-
)
|
145
|
-
|
146
|
-
|
147
|
-
@dataclass
|
148
|
-
class FullContext:
|
149
|
-
"""完整的上下文信息,包含用户上下文和请求上下文"""
|
150
|
-
user_context: Optional[UserContext] = None
|
151
|
-
request_context: Optional[RequestContext] = None
|
152
|
-
|
153
|
-
def to_metadata(self) -> Dict[str, str]:
|
154
|
-
"""转换为gRPC metadata格式"""
|
155
|
-
metadata = {}
|
156
|
-
|
157
|
-
if self.user_context:
|
158
|
-
metadata.update(self.user_context.to_metadata())
|
159
|
-
|
160
|
-
if self.request_context:
|
161
|
-
metadata.update(self.request_context.to_metadata())
|
162
|
-
|
163
|
-
return metadata
|
164
|
-
|
165
|
-
@classmethod
|
166
|
-
def from_metadata(cls, metadata: Dict[str, str]) -> 'FullContext':
|
167
|
-
"""从metadata中解析完整上下文"""
|
168
|
-
return cls(
|
169
|
-
user_context=UserContext.from_metadata(metadata),
|
170
|
-
request_context=RequestContext.from_metadata(metadata)
|
171
|
-
)
|
1
|
+
"""
|
2
|
+
用户上下文和请求上下文相关的Schema定义
|
3
|
+
"""
|
4
|
+
from dataclasses import dataclass, field
|
5
|
+
from typing import Optional, Dict, Any
|
6
|
+
from datetime import datetime
|
7
|
+
|
8
|
+
from file_hub_client.enums import Role
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class UserContext:
|
13
|
+
"""
|
14
|
+
用户上下文信息
|
15
|
+
|
16
|
+
包含两大类信息:
|
17
|
+
1. ownership(所有权): org_id, user_id - 表示资源归属
|
18
|
+
2. operator(操作者): actor_id, role - 表示实际操作者(可能是用户、agent或系统)
|
19
|
+
3. request info(请求信息): user_ip - 表示请求来源IP(用于审计和安全)
|
20
|
+
"""
|
21
|
+
# Ownership - 资源所有权信息
|
22
|
+
org_id: str # 组织ID
|
23
|
+
user_id: str # 用户ID
|
24
|
+
|
25
|
+
# Operator - 操作者信息
|
26
|
+
actor_id: Optional[str] = None # 实际操作者ID(如果为空,默认使用user_id)
|
27
|
+
role: Role = Role.ACCOUNT # 操作者角色(ACCOUNT, AGENT, SYSTEM等)
|
28
|
+
|
29
|
+
# Request info - 请求信息
|
30
|
+
user_ip: Optional[str] = None # 用户IP地址(请求来源IP)
|
31
|
+
|
32
|
+
def __post_init__(self):
|
33
|
+
"""初始化后处理,如果actor_id为空,默认使用user_id"""
|
34
|
+
if self.actor_id is None:
|
35
|
+
self.actor_id = self.user_id
|
36
|
+
|
37
|
+
def to_metadata(self) -> Dict[str, str]:
|
38
|
+
"""转换为gRPC metadata格式"""
|
39
|
+
metadata = {
|
40
|
+
'x-org-id': self.org_id,
|
41
|
+
'x-user-id': self.user_id,
|
42
|
+
'x-actor-id': self.actor_id,
|
43
|
+
'x-role': self.role,
|
44
|
+
}
|
45
|
+
|
46
|
+
# 只有当user_ip不为None时才添加x-user-ip
|
47
|
+
if self.user_ip is not None:
|
48
|
+
metadata['x-user-ip'] = self.user_ip
|
49
|
+
|
50
|
+
return metadata
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def from_metadata(cls, metadata: Dict[str, str]) -> Optional['UserContext']:
|
54
|
+
"""从metadata中解析用户上下文"""
|
55
|
+
org_id = metadata.get('x-org-id')
|
56
|
+
user_id = metadata.get('x-user-id')
|
57
|
+
|
58
|
+
if not org_id or not user_id:
|
59
|
+
return None
|
60
|
+
|
61
|
+
return cls(
|
62
|
+
org_id=org_id,
|
63
|
+
user_id=user_id,
|
64
|
+
actor_id=metadata.get('x-actor-id'),
|
65
|
+
role=Role(metadata.get('x-role', Role.ACCOUNT)),
|
66
|
+
user_ip=metadata.get('x-user-ip')
|
67
|
+
)
|
68
|
+
|
69
|
+
|
70
|
+
@dataclass
|
71
|
+
class RequestContext:
|
72
|
+
"""
|
73
|
+
请求上下文信息
|
74
|
+
|
75
|
+
包含请求相关的元数据,如客户端信息、请求追踪等
|
76
|
+
"""
|
77
|
+
request_id: Optional[str] = None # 请求ID,用于追踪
|
78
|
+
client_ip: Optional[str] = None # 客户端IP地址
|
79
|
+
client_version: Optional[str] = None # 客户端版本
|
80
|
+
client_type: Optional[str] = None # 客户端类型(web, mobile, desktop, cli等)
|
81
|
+
user_agent: Optional[str] = None # User-Agent信息
|
82
|
+
timestamp: Optional[datetime] = field(default_factory=datetime.now) # 请求时间戳
|
83
|
+
extra: Dict[str, Any] = field(default_factory=dict) # 其他扩展信息
|
84
|
+
|
85
|
+
def to_metadata(self) -> Dict[str, str]:
|
86
|
+
"""转换为gRPC metadata格式"""
|
87
|
+
metadata = {}
|
88
|
+
|
89
|
+
if self.request_id:
|
90
|
+
metadata['x-request-id'] = self.request_id
|
91
|
+
if self.client_ip:
|
92
|
+
metadata['x-client-ip'] = self.client_ip
|
93
|
+
if self.client_version:
|
94
|
+
metadata['x-client-version'] = self.client_version
|
95
|
+
if self.client_type:
|
96
|
+
metadata['x-client-type'] = self.client_type
|
97
|
+
if self.user_agent:
|
98
|
+
metadata['x-user-agent'] = self.user_agent
|
99
|
+
if self.timestamp:
|
100
|
+
metadata['x-timestamp'] = self.timestamp.isoformat()
|
101
|
+
|
102
|
+
# 添加扩展信息
|
103
|
+
for key, value in self.extra.items():
|
104
|
+
metadata[f'x-{key}'] = str(value)
|
105
|
+
|
106
|
+
return metadata
|
107
|
+
|
108
|
+
@classmethod
|
109
|
+
def from_metadata(cls, metadata: Dict[str, str]) -> 'RequestContext':
|
110
|
+
"""从metadata中解析请求上下文"""
|
111
|
+
# 提取标准字段
|
112
|
+
request_id = metadata.get('x-request-id')
|
113
|
+
client_ip = metadata.get('x-client-ip')
|
114
|
+
client_version = metadata.get('x-client-version')
|
115
|
+
client_type = metadata.get('x-client-type')
|
116
|
+
user_agent = metadata.get('x-user-agent')
|
117
|
+
|
118
|
+
# 解析时间戳
|
119
|
+
timestamp = None
|
120
|
+
if 'x-timestamp' in metadata:
|
121
|
+
try:
|
122
|
+
timestamp = datetime.fromisoformat(metadata['x-timestamp'])
|
123
|
+
except:
|
124
|
+
pass
|
125
|
+
|
126
|
+
# 提取扩展字段
|
127
|
+
extra = {}
|
128
|
+
for key, value in metadata.items():
|
129
|
+
if key.startswith('x-') and key not in [
|
130
|
+
'x-request-id', 'x-client-ip', 'x-client-version',
|
131
|
+
'x-client-type', 'x-user-agent', 'x-timestamp',
|
132
|
+
'x-org-id', 'x-user-id', 'x-actor-id', 'x-role'
|
133
|
+
]:
|
134
|
+
extra[key[2:]] = value # 去掉 'x-' 前缀
|
135
|
+
|
136
|
+
return cls(
|
137
|
+
request_id=request_id,
|
138
|
+
client_ip=client_ip,
|
139
|
+
client_version=client_version,
|
140
|
+
client_type=client_type,
|
141
|
+
user_agent=user_agent,
|
142
|
+
timestamp=timestamp,
|
143
|
+
extra=extra
|
144
|
+
)
|
145
|
+
|
146
|
+
|
147
|
+
@dataclass
|
148
|
+
class FullContext:
|
149
|
+
"""完整的上下文信息,包含用户上下文和请求上下文"""
|
150
|
+
user_context: Optional[UserContext] = None
|
151
|
+
request_context: Optional[RequestContext] = None
|
152
|
+
|
153
|
+
def to_metadata(self) -> Dict[str, str]:
|
154
|
+
"""转换为gRPC metadata格式"""
|
155
|
+
metadata = {}
|
156
|
+
|
157
|
+
if self.user_context:
|
158
|
+
metadata.update(self.user_context.to_metadata())
|
159
|
+
|
160
|
+
if self.request_context:
|
161
|
+
metadata.update(self.request_context.to_metadata())
|
162
|
+
|
163
|
+
return metadata
|
164
|
+
|
165
|
+
@classmethod
|
166
|
+
def from_metadata(cls, metadata: Dict[str, str]) -> 'FullContext':
|
167
|
+
"""从metadata中解析完整上下文"""
|
168
|
+
return cls(
|
169
|
+
user_context=UserContext.from_metadata(metadata),
|
170
|
+
request_context=RequestContext.from_metadata(metadata)
|
171
|
+
)
|
@@ -453,18 +453,33 @@ class AsyncBlobService(BaseFileService):
|
|
453
453
|
# 使用下载的内容作为file参数
|
454
454
|
file = downloaded_content
|
455
455
|
|
456
|
-
#
|
457
|
-
mime_type
|
456
|
+
# MIME类型优先级:用户指定 > 内容检测 > URL文件名推断
|
457
|
+
if mime_type:
|
458
|
+
# 用户明确提供的MIME类型优先级最高,无需进行内容检测
|
459
|
+
final_mime_type = mime_type
|
460
|
+
else:
|
461
|
+
# 用户未提供MIME类型,进行内容检测和文件名推断
|
462
|
+
content_detected_mime = self._detect_mime_from_content(downloaded_content)
|
463
|
+
url_filename_mime = get_file_mime_type(Path(file_name))
|
464
|
+
|
465
|
+
if content_detected_mime != "application/octet-stream":
|
466
|
+
# 内容检测到了具体的MIME类型,使用内容检测的
|
467
|
+
final_mime_type = content_detected_mime
|
468
|
+
else:
|
469
|
+
# 内容检测失败,使用从URL文件名推断的MIME类型
|
470
|
+
final_mime_type = url_filename_mime
|
458
471
|
|
459
|
-
#
|
460
|
-
_, content, file_size,
|
472
|
+
# 提取文件信息,传入最终确定的MIME类型
|
473
|
+
_, content, file_size, extract_mime_type, extract_file_type, file_hash = self._extract_file_info(file, final_mime_type)
|
461
474
|
|
462
475
|
# file_name已经在上面设置了(要么是用户指定的,要么是从URL提取的)
|
463
476
|
extracted_file_name = file_name
|
477
|
+
|
478
|
+
# 使用最终确定的MIME类型
|
479
|
+
mime_type = final_mime_type
|
464
480
|
|
465
|
-
#
|
466
|
-
file_type =
|
467
|
-
extracted_file_name).suffix else 'dat'
|
481
|
+
# 基于最终MIME类型计算文件扩展名
|
482
|
+
file_type = extract_file_type
|
468
483
|
else:
|
469
484
|
# 解析文件参数,提取文件信息
|
470
485
|
# 如果用户指定了文件名,先从文件名推断MIME类型,然后传给_extract_file_info
|
@@ -485,7 +500,9 @@ class AsyncBlobService(BaseFileService):
|
|
485
500
|
# 没有指定文件名,传入用户提供的MIME类型(如果有)
|
486
501
|
extracted_file_name, content, file_size, extract_mime_type, extract_file_type, file_hash = self._extract_file_info(
|
487
502
|
file, mime_type)
|
488
|
-
|
503
|
+
# 如果用户指定了MIME类型,使用用户指定的,否则使用检测的
|
504
|
+
if not mime_type:
|
505
|
+
mime_type = extract_mime_type
|
489
506
|
file_type = extract_file_type
|
490
507
|
|
491
508
|
# 根据文件大小自动选择上传模式
|
@@ -452,18 +452,33 @@ class SyncBlobService(BaseFileService):
|
|
452
452
|
# 使用下载的内容作为file参数
|
453
453
|
file = downloaded_content
|
454
454
|
|
455
|
-
#
|
456
|
-
mime_type
|
455
|
+
# MIME类型优先级:用户指定 > 内容检测 > URL文件名推断
|
456
|
+
if mime_type:
|
457
|
+
# 用户明确提供的MIME类型优先级最高,无需进行内容检测
|
458
|
+
final_mime_type = mime_type
|
459
|
+
else:
|
460
|
+
# 用户未提供MIME类型,进行内容检测和文件名推断
|
461
|
+
content_detected_mime = self._detect_mime_from_content(downloaded_content)
|
462
|
+
url_filename_mime = get_file_mime_type(Path(file_name))
|
463
|
+
|
464
|
+
if content_detected_mime != "application/octet-stream":
|
465
|
+
# 内容检测到了具体的MIME类型,使用内容检测的
|
466
|
+
final_mime_type = content_detected_mime
|
467
|
+
else:
|
468
|
+
# 内容检测失败,使用从URL文件名推断的MIME类型
|
469
|
+
final_mime_type = url_filename_mime
|
457
470
|
|
458
|
-
#
|
459
|
-
_, content, file_size,
|
471
|
+
# 提取文件信息,传入最终确定的MIME类型
|
472
|
+
_, content, file_size, extract_mime_type, extract_file_type, file_hash = self._extract_file_info(file, final_mime_type)
|
460
473
|
|
461
474
|
# file_name已经在上面设置了(要么是用户指定的,要么是从URL提取的)
|
462
475
|
extracted_file_name = file_name
|
476
|
+
|
477
|
+
# 使用最终确定的MIME类型
|
478
|
+
mime_type = final_mime_type
|
463
479
|
|
464
|
-
#
|
465
|
-
file_type =
|
466
|
-
extracted_file_name).suffix else 'dat'
|
480
|
+
# 基于最终MIME类型计算文件扩展名
|
481
|
+
file_type = extract_file_type
|
467
482
|
else:
|
468
483
|
# 解析文件参数,提取文件信息
|
469
484
|
# 如果用户指定了文件名,先从文件名推断MIME类型,然后传给_extract_file_info
|
@@ -484,7 +499,9 @@ class SyncBlobService(BaseFileService):
|
|
484
499
|
# 没有指定文件名,传入用户提供的MIME类型(如果有)
|
485
500
|
extracted_file_name, content, file_size, extract_mime_type, extract_file_type, file_hash = self._extract_file_info(
|
486
501
|
file, mime_type)
|
487
|
-
|
502
|
+
# 如果用户指定了MIME类型,使用用户指定的,否则使用检测的
|
503
|
+
if not mime_type:
|
504
|
+
mime_type = extract_mime_type
|
488
505
|
file_type = extract_file_type
|
489
506
|
|
490
507
|
# 根据文件大小自动选择上传模式
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
2
2
|
|
3
3
|
setup(
|
4
4
|
name="tamar-file-hub-client",
|
5
|
-
version="0.1.
|
5
|
+
version="0.1.7",
|
6
6
|
description="A Python SDK for gRPC-based file management system",
|
7
7
|
long_description=open("README.md", encoding="utf-8").read(),
|
8
8
|
long_description_content_type="text/markdown",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/__init__.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/export_format.py
RENAMED
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/enums/upload_mode.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/errors/__init__.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/errors/exceptions.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/async_client.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/gen/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/interceptors.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/rpc/sync_client.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/__init__.py
RENAMED
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/folder.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/schemas/taple.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/services/__init__.py
RENAMED
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
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/__init__.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/converter.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/download_helper.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/file_utils.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/idempotency.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/ip_detector.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/logging.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/smart_retry.py
RENAMED
File without changes
|
{tamar_file_hub_client-0.1.6 → tamar_file_hub_client-0.1.7}/file_hub_client/utils/upload_helper.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|