isa-common 0.1.0__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.
- isa_common/__init__.py +183 -0
- isa_common/base_client.py +145 -0
- isa_common/duckdb_client.py +399 -0
- isa_common/events/__init__.py +61 -0
- isa_common/events/base_event_models.py +107 -0
- isa_common/events/base_event_publisher.py +199 -0
- isa_common/events/base_event_subscriber.py +500 -0
- isa_common/events/billing_event_publisher.py +451 -0
- isa_common/events/billing_events.py +286 -0
- isa_common/minio_client.py +223 -0
- isa_common/mqtt_client.py +220 -0
- isa_common/nats_client.py +260 -0
- isa_common/proto/__init__.py +22 -0
- isa_common/proto/common_pb2.py +63 -0
- isa_common/proto/common_pb2_grpc.py +24 -0
- isa_common/proto/duckdb_service_pb2.py +208 -0
- isa_common/proto/duckdb_service_pb2_grpc.py +1310 -0
- isa_common/proto/loki_service_pb2.py +178 -0
- isa_common/proto/loki_service_pb2_grpc.py +930 -0
- isa_common/proto/minio_service_pb2.py +162 -0
- isa_common/proto/minio_service_pb2_grpc.py +959 -0
- isa_common/proto/mqtt_service_pb2.py +174 -0
- isa_common/proto/mqtt_service_pb2_grpc.py +1137 -0
- isa_common/proto/nats_service_pb2.py +220 -0
- isa_common/proto/nats_service_pb2_grpc.py +1436 -0
- isa_common/proto/redis_service_pb2.py +308 -0
- isa_common/proto/redis_service_pb2_grpc.py +2725 -0
- isa_common/proto/supabase_service_pb2.py +96 -0
- isa_common/proto/supabase_service_pb2_grpc.py +671 -0
- isa_common/supabase_client.py +428 -0
- isa_common-0.1.0.dist-info/METADATA +180 -0
- isa_common-0.1.0.dist-info/RECORD +34 -0
- isa_common-0.1.0.dist-info/WHEEL +5 -0
- isa_common-0.1.0.dist-info/top_level.txt +1 -0
isa_common/__init__.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
gRPC Clients Package
|
|
4
|
+
统一的 gRPC 客户端接口
|
|
5
|
+
|
|
6
|
+
使用示例:
|
|
7
|
+
from core.clients import get_client, SupabaseClient, MinIOClient
|
|
8
|
+
|
|
9
|
+
# 方式 1: 使用工厂函数
|
|
10
|
+
supabase = get_client('supabase', user_id='user_123')
|
|
11
|
+
|
|
12
|
+
# 方式 2: 直接实例化
|
|
13
|
+
minio = MinIOClient(host='localhost', port=50051, user_id='user_123')
|
|
14
|
+
|
|
15
|
+
# 方式 3: 使用 with 语句自动管理连接
|
|
16
|
+
with SupabaseClient() as client:
|
|
17
|
+
client.query('users', select='*')
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Optional, Dict
|
|
21
|
+
from .base_client import BaseGRPCClient
|
|
22
|
+
from .supabase_client import SupabaseClient
|
|
23
|
+
from .minio_client import MinIOClient
|
|
24
|
+
from .duckdb_client import DuckDBClient
|
|
25
|
+
from .mqtt_client import MQTTClient
|
|
26
|
+
from .nats_client import NATSClient
|
|
27
|
+
|
|
28
|
+
# Import events module (but don't unpack, keep as submodule)
|
|
29
|
+
from . import events
|
|
30
|
+
|
|
31
|
+
# 导出所有客户端
|
|
32
|
+
__all__ = [
|
|
33
|
+
'BaseGRPCClient',
|
|
34
|
+
'SupabaseClient',
|
|
35
|
+
'MinIOClient',
|
|
36
|
+
'DuckDBClient',
|
|
37
|
+
'MQTTClient',
|
|
38
|
+
'NATSClient',
|
|
39
|
+
'get_client',
|
|
40
|
+
'ClientFactory',
|
|
41
|
+
'events', # Export events submodule
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# 默认配置
|
|
45
|
+
DEFAULT_PORTS: Dict[str, int] = {
|
|
46
|
+
'minio': 50051,
|
|
47
|
+
'duckdb': 50052,
|
|
48
|
+
'mqtt': 50053,
|
|
49
|
+
'loki': 50054,
|
|
50
|
+
'redis': 50055,
|
|
51
|
+
'nats': 50056,
|
|
52
|
+
'supabase': 50057,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
DEFAULT_HOST = 'localhost'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ClientFactory:
|
|
59
|
+
"""gRPC 客户端工厂"""
|
|
60
|
+
|
|
61
|
+
# 客户端映射
|
|
62
|
+
_clients = {
|
|
63
|
+
'supabase': SupabaseClient,
|
|
64
|
+
'minio': MinIOClient,
|
|
65
|
+
'duckdb': DuckDBClient,
|
|
66
|
+
'mqtt': MQTTClient,
|
|
67
|
+
'nats': NATSClient,
|
|
68
|
+
# TODO: 添加其他客户端
|
|
69
|
+
# 'redis': RedisClient,
|
|
70
|
+
# 'loki': LokiClient,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def create(cls, service_name: str, host: Optional[str] = None,
|
|
75
|
+
port: Optional[int] = None, user_id: Optional[str] = None) -> BaseGRPCClient:
|
|
76
|
+
"""
|
|
77
|
+
创建 gRPC 客户端
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
service_name: 服务名称 (supabase, minio, duckdb, etc.)
|
|
81
|
+
host: 服务地址 (默认: localhost)
|
|
82
|
+
port: 服务端口 (默认: 根据服务自动选择)
|
|
83
|
+
user_id: 用户 ID
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
客户端实例
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError: 如果服务名称不支持
|
|
90
|
+
|
|
91
|
+
示例:
|
|
92
|
+
client = ClientFactory.create('supabase', user_id='user_123')
|
|
93
|
+
client = ClientFactory.create('minio', host='192.168.1.100', port=50051)
|
|
94
|
+
"""
|
|
95
|
+
service_name = service_name.lower()
|
|
96
|
+
|
|
97
|
+
if service_name not in cls._clients:
|
|
98
|
+
available = ', '.join(cls._clients.keys())
|
|
99
|
+
raise ValueError(f"不支持的服务: {service_name}. 可用服务: {available}")
|
|
100
|
+
|
|
101
|
+
# 使用默认值
|
|
102
|
+
if host is None:
|
|
103
|
+
host = DEFAULT_HOST
|
|
104
|
+
if port is None:
|
|
105
|
+
port = DEFAULT_PORTS.get(service_name, 50051)
|
|
106
|
+
|
|
107
|
+
client_class = cls._clients[service_name]
|
|
108
|
+
return client_class(host=host, port=port, user_id=user_id)
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def register_client(cls, service_name: str, client_class):
|
|
112
|
+
"""
|
|
113
|
+
注册新的客户端类
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
service_name: 服务名称
|
|
117
|
+
client_class: 客户端类 (必须继承 BaseGRPCClient)
|
|
118
|
+
"""
|
|
119
|
+
if not issubclass(client_class, BaseGRPCClient):
|
|
120
|
+
raise TypeError(f"{client_class} 必须继承 BaseGRPCClient")
|
|
121
|
+
|
|
122
|
+
cls._clients[service_name.lower()] = client_class
|
|
123
|
+
print(f"✅ 注册客户端: {service_name} -> {client_class.__name__}")
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def list_services(cls) -> list:
|
|
127
|
+
"""列出所有可用的服务"""
|
|
128
|
+
return list(cls._clients.keys())
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# 便捷函数
|
|
132
|
+
def get_client(service_name: str, host: Optional[str] = None,
|
|
133
|
+
port: Optional[int] = None, user_id: Optional[str] = None) -> BaseGRPCClient:
|
|
134
|
+
"""
|
|
135
|
+
获取 gRPC 客户端 (便捷函数)
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
service_name: 服务名称
|
|
139
|
+
host: 服务地址
|
|
140
|
+
port: 服务端口
|
|
141
|
+
user_id: 用户 ID
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
客户端实例
|
|
145
|
+
|
|
146
|
+
示例:
|
|
147
|
+
from core.clients import get_client
|
|
148
|
+
|
|
149
|
+
supabase = get_client('supabase', user_id='user_123')
|
|
150
|
+
minio = get_client('minio', host='192.168.1.100')
|
|
151
|
+
"""
|
|
152
|
+
return ClientFactory.create(service_name, host, port, user_id)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# 显示所有可用服务
|
|
156
|
+
def show_available_services():
|
|
157
|
+
"""显示所有可用的 gRPC 服务"""
|
|
158
|
+
print("📦 可用的 gRPC 服务:")
|
|
159
|
+
print()
|
|
160
|
+
for service in sorted(ClientFactory.list_services()):
|
|
161
|
+
port = DEFAULT_PORTS.get(service, 'N/A')
|
|
162
|
+
client_class = ClientFactory._clients[service]
|
|
163
|
+
print(f" • {service:12} (端口: {port}) -> {client_class.__name__}")
|
|
164
|
+
print()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
if __name__ == '__main__':
|
|
168
|
+
# 显示可用服务
|
|
169
|
+
show_available_services()
|
|
170
|
+
|
|
171
|
+
# 测试客户端创建
|
|
172
|
+
print("测试客户端创建:")
|
|
173
|
+
print("-" * 60)
|
|
174
|
+
|
|
175
|
+
# 使用工厂创建客户端
|
|
176
|
+
with get_client('supabase', user_id='test_user') as supabase:
|
|
177
|
+
supabase.health_check()
|
|
178
|
+
|
|
179
|
+
print()
|
|
180
|
+
|
|
181
|
+
with get_client('minio', user_id='test_user') as minio:
|
|
182
|
+
minio.health_check()
|
|
183
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Base gRPC Client
|
|
4
|
+
所有 gRPC 客户端的基类,提供统一的连接管理和错误处理
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import grpc
|
|
8
|
+
import logging
|
|
9
|
+
import threading
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
|
|
13
|
+
|
|
14
|
+
# Configure logging
|
|
15
|
+
logging.basicConfig(
|
|
16
|
+
level=logging.INFO,
|
|
17
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
18
|
+
)
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BaseGRPCClient(ABC):
|
|
23
|
+
"""gRPC 客户端基类"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, host: str = 'localhost', port: int = 50051, user_id: Optional[str] = None,
|
|
26
|
+
lazy_connect: bool = True, enable_compression: bool = True, enable_retry: bool = True):
|
|
27
|
+
"""
|
|
28
|
+
初始化 gRPC 客户端
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
host: 服务地址
|
|
32
|
+
port: 服务端口
|
|
33
|
+
user_id: 用户 ID (用于多租户隔离)
|
|
34
|
+
lazy_connect: 是否延迟连接 (默认: True, 更快的启动速度)
|
|
35
|
+
enable_compression: 是否启用 gRPC 压缩 (默认: True)
|
|
36
|
+
enable_retry: 是否启用重试逻辑 (默认: True)
|
|
37
|
+
"""
|
|
38
|
+
self.host = host
|
|
39
|
+
self.port = port
|
|
40
|
+
self.user_id = user_id or 'default_user'
|
|
41
|
+
self.address = f'{host}:{port}'
|
|
42
|
+
self.enable_compression = enable_compression
|
|
43
|
+
self.enable_retry = enable_retry
|
|
44
|
+
|
|
45
|
+
# Lazy initialization
|
|
46
|
+
self.channel = None
|
|
47
|
+
self.stub = None
|
|
48
|
+
self._connect_lock = threading.Lock()
|
|
49
|
+
self._connected = False
|
|
50
|
+
|
|
51
|
+
# Connect immediately if not lazy
|
|
52
|
+
if not lazy_connect:
|
|
53
|
+
self._ensure_connected()
|
|
54
|
+
|
|
55
|
+
def _ensure_connected(self):
|
|
56
|
+
"""确保已连接(线程安全的延迟连接)"""
|
|
57
|
+
if self._connected and self.channel is not None:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
with self._connect_lock:
|
|
61
|
+
# Double-check after acquiring lock
|
|
62
|
+
if self._connected and self.channel is not None:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
logger.info(f"[{self.service_name()}] Connecting to {self.address}...")
|
|
66
|
+
|
|
67
|
+
# Build channel options
|
|
68
|
+
options = [
|
|
69
|
+
('grpc.max_receive_message_length', 100 * 1024 * 1024), # 100MB
|
|
70
|
+
('grpc.max_send_message_length', 100 * 1024 * 1024), # 100MB
|
|
71
|
+
('grpc.keepalive_time_ms', 10000),
|
|
72
|
+
('grpc.keepalive_timeout_ms', 5000),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
# Add compression if enabled
|
|
76
|
+
if self.enable_compression:
|
|
77
|
+
options.append(('grpc.default_compression_algorithm', grpc.Compression.Gzip))
|
|
78
|
+
options.append(('grpc.default_compression_level', grpc.Compression.Gzip))
|
|
79
|
+
|
|
80
|
+
# Create channel
|
|
81
|
+
self.channel = grpc.insecure_channel(self.address, options=options)
|
|
82
|
+
|
|
83
|
+
# Create stub
|
|
84
|
+
self.stub = self._create_stub()
|
|
85
|
+
|
|
86
|
+
# Mark as connected
|
|
87
|
+
self._connected = True
|
|
88
|
+
logger.info(f"[{self.service_name()}] Connected successfully to {self.address}")
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def _create_stub(self):
|
|
92
|
+
"""子类实现:创建特定服务的 stub"""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def service_name(self) -> str:
|
|
97
|
+
"""子类实现:返回服务名称"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
def _call_with_retry(self, func, *args, **kwargs):
|
|
101
|
+
"""带重试的 RPC 调用"""
|
|
102
|
+
if not self.enable_retry:
|
|
103
|
+
return func(*args, **kwargs)
|
|
104
|
+
|
|
105
|
+
@retry(
|
|
106
|
+
stop=stop_after_attempt(3),
|
|
107
|
+
wait=wait_exponential(multiplier=1, min=1, max=10),
|
|
108
|
+
retry=retry_if_exception_type(grpc.RpcError),
|
|
109
|
+
reraise=True
|
|
110
|
+
)
|
|
111
|
+
def _retry_wrapper():
|
|
112
|
+
self._ensure_connected()
|
|
113
|
+
return func(*args, **kwargs)
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
return _retry_wrapper()
|
|
117
|
+
except grpc.RpcError as e:
|
|
118
|
+
logger.error(f"[{self.service_name()}] RPC failed after retries: {e.code()} - {e.details()}")
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
def handle_error(self, e: Exception, operation: str = "操作"):
|
|
122
|
+
"""统一错误处理"""
|
|
123
|
+
logger.error(f"[{self.service_name()}] {operation} 失败:")
|
|
124
|
+
if isinstance(e, grpc.RpcError):
|
|
125
|
+
logger.error(f" 错误代码: {e.code()}")
|
|
126
|
+
logger.error(f" 错误详情: {e.details()}")
|
|
127
|
+
else:
|
|
128
|
+
logger.error(f" 错误: {e}")
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
def close(self):
|
|
132
|
+
"""关闭连接"""
|
|
133
|
+
if self.channel is not None:
|
|
134
|
+
self.channel.close()
|
|
135
|
+
self._connected = False
|
|
136
|
+
logger.info(f"[{self.service_name()}] Connection closed")
|
|
137
|
+
|
|
138
|
+
def __enter__(self):
|
|
139
|
+
"""支持 with 语句"""
|
|
140
|
+
return self
|
|
141
|
+
|
|
142
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
143
|
+
"""退出时自动关闭连接"""
|
|
144
|
+
self.close()
|
|
145
|
+
|