tamar-file-hub-client 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
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.
- file_hub_client/client.py +24 -4
- file_hub_client/rpc/async_client.py +31 -4
- file_hub_client/rpc/gen/file_service_pb2.py +30 -12
- file_hub_client/rpc/gen/file_service_pb2_grpc.py +173 -0
- file_hub_client/rpc/interceptors.py +578 -580
- file_hub_client/rpc/protos/file_service.proto +68 -1
- file_hub_client/rpc/sync_client.py +31 -4
- file_hub_client/schemas/__init__.py +10 -0
- file_hub_client/schemas/context.py +171 -160
- file_hub_client/schemas/file.py +171 -126
- file_hub_client/services/file/async_blob_service.py +260 -8
- file_hub_client/services/file/async_file_service.py +217 -0
- file_hub_client/services/file/sync_blob_service.py +261 -8
- file_hub_client/services/file/sync_file_service.py +217 -0
- file_hub_client/utils/__init__.py +14 -0
- file_hub_client/utils/file_utils.py +186 -153
- file_hub_client/utils/ip_detector.py +226 -0
- file_hub_client/utils/logging.py +335 -318
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/METADATA +178 -2
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/RECORD +22 -21
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/WHEEL +0 -0
- {tamar_file_hub_client-0.1.3.dist-info → tamar_file_hub_client-0.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
"""
|
2
|
+
用户真实IP自动检测模块
|
3
|
+
从当前HTTP请求上下文中自动获取真实用户IP地址
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import threading
|
8
|
+
from typing import Optional, Dict, Any, Callable
|
9
|
+
from contextvars import ContextVar
|
10
|
+
|
11
|
+
# 使用ContextVar存储当前请求的用户IP
|
12
|
+
current_user_ip: ContextVar[Optional[str]] = ContextVar('current_user_ip', default=None)
|
13
|
+
|
14
|
+
# 存储自定义IP提取器
|
15
|
+
_custom_ip_extractor: Optional[Callable[[], Optional[str]]] = None
|
16
|
+
|
17
|
+
# 线程本地存储(fallback)
|
18
|
+
_thread_local = threading.local()
|
19
|
+
|
20
|
+
|
21
|
+
def set_user_ip_extractor(extractor: Callable[[], Optional[str]]):
|
22
|
+
"""
|
23
|
+
设置自定义用户IP提取器
|
24
|
+
|
25
|
+
Args:
|
26
|
+
extractor: 返回用户IP的函数,如果无法获取则返回None
|
27
|
+
"""
|
28
|
+
global _custom_ip_extractor
|
29
|
+
_custom_ip_extractor = extractor
|
30
|
+
|
31
|
+
|
32
|
+
def set_current_user_ip(ip: str):
|
33
|
+
"""
|
34
|
+
设置当前请求的用户IP(通常在请求开始时调用)
|
35
|
+
|
36
|
+
Args:
|
37
|
+
ip: 用户真实IP地址
|
38
|
+
"""
|
39
|
+
current_user_ip.set(ip)
|
40
|
+
# 同时设置线程本地存储作为fallback
|
41
|
+
_thread_local.user_ip = ip
|
42
|
+
|
43
|
+
|
44
|
+
def get_current_user_ip() -> Optional[str]:
|
45
|
+
"""
|
46
|
+
自动获取当前用户的真实IP地址
|
47
|
+
|
48
|
+
优先级:
|
49
|
+
1. ContextVar中的用户IP
|
50
|
+
2. 自定义IP提取器
|
51
|
+
3. 常见Web框架自动检测
|
52
|
+
4. 环境变量
|
53
|
+
5. 线程本地存储
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
用户真实IP地址,如果无法获取则返回None
|
57
|
+
"""
|
58
|
+
# 1. 优先使用ContextVar
|
59
|
+
ip = current_user_ip.get(None)
|
60
|
+
if ip:
|
61
|
+
return ip
|
62
|
+
|
63
|
+
# 2. 尝试自定义提取器
|
64
|
+
if _custom_ip_extractor:
|
65
|
+
try:
|
66
|
+
ip = _custom_ip_extractor()
|
67
|
+
if ip:
|
68
|
+
return ip
|
69
|
+
except:
|
70
|
+
pass
|
71
|
+
|
72
|
+
# 3. 尝试从常见Web框架中自动获取
|
73
|
+
ip = _auto_detect_from_web_frameworks()
|
74
|
+
if ip:
|
75
|
+
return ip
|
76
|
+
|
77
|
+
# 4. 尝试从环境变量获取
|
78
|
+
ip = os.environ.get('USER_IP') or os.environ.get('CLIENT_IP')
|
79
|
+
if ip:
|
80
|
+
return ip
|
81
|
+
|
82
|
+
# 5. Fallback到线程本地存储
|
83
|
+
try:
|
84
|
+
return getattr(_thread_local, 'user_ip', None)
|
85
|
+
except:
|
86
|
+
return None
|
87
|
+
|
88
|
+
|
89
|
+
def _auto_detect_from_web_frameworks() -> Optional[str]:
|
90
|
+
"""
|
91
|
+
从常见Web框架中自动检测用户IP
|
92
|
+
"""
|
93
|
+
# Flask
|
94
|
+
try:
|
95
|
+
from flask import request
|
96
|
+
if request:
|
97
|
+
return _extract_ip_from_headers(request.environ)
|
98
|
+
except (ImportError, RuntimeError):
|
99
|
+
pass
|
100
|
+
|
101
|
+
# Django
|
102
|
+
try:
|
103
|
+
from django.http import HttpRequest
|
104
|
+
from django.utils.deprecation import MiddlewareMixin
|
105
|
+
# Django需要通过中间件设置,这里只能检查是否有请求对象
|
106
|
+
import django
|
107
|
+
from django.core.context_processors import request as django_request
|
108
|
+
# Django的请求需要通过其他方式获取,这里先跳过
|
109
|
+
except ImportError:
|
110
|
+
pass
|
111
|
+
|
112
|
+
# FastAPI/Starlette
|
113
|
+
try:
|
114
|
+
from starlette.requests import Request
|
115
|
+
# FastAPI需要在路由处理器中获取,这里先跳过
|
116
|
+
except ImportError:
|
117
|
+
pass
|
118
|
+
|
119
|
+
# Tornado
|
120
|
+
try:
|
121
|
+
import tornado.web
|
122
|
+
# Tornado需要在RequestHandler中获取,这里先跳过
|
123
|
+
except ImportError:
|
124
|
+
pass
|
125
|
+
|
126
|
+
return None
|
127
|
+
|
128
|
+
|
129
|
+
def _extract_ip_from_headers(environ: Dict[str, Any]) -> Optional[str]:
|
130
|
+
"""
|
131
|
+
从HTTP环境变量中提取用户真实IP
|
132
|
+
|
133
|
+
Args:
|
134
|
+
environ: WSGI environ字典或类似的HTTP环境变量
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
用户真实IP,优先级: X-Forwarded-For > X-Real-IP > CF-Connecting-IP > Remote-Addr
|
138
|
+
"""
|
139
|
+
# X-Forwarded-For: 最常用的代理头,包含原始客户端IP
|
140
|
+
forwarded_for = environ.get('HTTP_X_FORWARDED_FOR')
|
141
|
+
if forwarded_for:
|
142
|
+
# 取第一个IP(原始客户端IP),忽略代理IP
|
143
|
+
return forwarded_for.split(',')[0].strip()
|
144
|
+
|
145
|
+
# X-Real-IP: Nginx常用的真实IP头
|
146
|
+
real_ip = environ.get('HTTP_X_REAL_IP')
|
147
|
+
if real_ip:
|
148
|
+
return real_ip.strip()
|
149
|
+
|
150
|
+
# CF-Connecting-IP: Cloudflare的连接IP
|
151
|
+
cf_ip = environ.get('HTTP_CF_CONNECTING_IP')
|
152
|
+
if cf_ip:
|
153
|
+
return cf_ip.strip()
|
154
|
+
|
155
|
+
# Remote-Addr: 直接连接的IP(可能是代理IP)
|
156
|
+
remote_addr = environ.get('REMOTE_ADDR')
|
157
|
+
if remote_addr:
|
158
|
+
return remote_addr.strip()
|
159
|
+
|
160
|
+
return None
|
161
|
+
|
162
|
+
|
163
|
+
def clear_current_user_ip():
|
164
|
+
"""清除当前请求的用户IP(通常在请求结束时调用)"""
|
165
|
+
current_user_ip.set(None)
|
166
|
+
try:
|
167
|
+
delattr(_thread_local, 'user_ip')
|
168
|
+
except AttributeError:
|
169
|
+
pass
|
170
|
+
|
171
|
+
|
172
|
+
# Flask集成装饰器
|
173
|
+
def flask_auto_user_ip(app=None):
|
174
|
+
"""
|
175
|
+
Flask应用自动用户IP检测装饰器
|
176
|
+
|
177
|
+
用法:
|
178
|
+
from flask import Flask
|
179
|
+
from file_hub_client.utils.ip_detector import flask_auto_user_ip
|
180
|
+
|
181
|
+
app = Flask(__name__)
|
182
|
+
flask_auto_user_ip(app)
|
183
|
+
"""
|
184
|
+
def decorator(app_instance):
|
185
|
+
@app_instance.before_request
|
186
|
+
def extract_user_ip():
|
187
|
+
from flask import request
|
188
|
+
ip = _extract_ip_from_headers(request.environ)
|
189
|
+
if ip:
|
190
|
+
set_current_user_ip(ip)
|
191
|
+
|
192
|
+
@app_instance.after_request
|
193
|
+
def clear_user_ip(response):
|
194
|
+
clear_current_user_ip()
|
195
|
+
return response
|
196
|
+
|
197
|
+
return app_instance
|
198
|
+
|
199
|
+
if app is None:
|
200
|
+
return decorator
|
201
|
+
else:
|
202
|
+
return decorator(app)
|
203
|
+
|
204
|
+
|
205
|
+
# 上下文管理器
|
206
|
+
class UserIPContext:
|
207
|
+
"""
|
208
|
+
用户IP上下文管理器
|
209
|
+
|
210
|
+
用法:
|
211
|
+
with UserIPContext("192.168.1.100"):
|
212
|
+
# 在此范围内SDK会自动使用这个IP
|
213
|
+
client.upload_file(...)
|
214
|
+
"""
|
215
|
+
|
216
|
+
def __init__(self, user_ip: str):
|
217
|
+
self.user_ip = user_ip
|
218
|
+
self.token = None
|
219
|
+
|
220
|
+
def __enter__(self):
|
221
|
+
self.token = current_user_ip.set(self.user_ip)
|
222
|
+
return self
|
223
|
+
|
224
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
225
|
+
if self.token:
|
226
|
+
current_user_ip.reset(self.token)
|