misakadb 0.1.1__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.
misakadb/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """Python client and MiQL query builder for MisakaDB."""
2
+
3
+ from .apis.api import MisakaDBClient
4
+ from .mql.MQ import MiQL
5
+ from .network.command_send import commandSend
6
+ from .network.sock import clientCore
7
+ from .sdk import Connection, MisakaError, conn, getCurrent
8
+
9
+ __version__ = "0.1.1"
10
+ __all__ = [
11
+ "conn", "getCurrent", "Connection", "MisakaError",
12
+ "MisakaDBClient", "MiQL", "clientCore", "commandSend", "__version__",
13
+ ]
misakadb/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1 @@
1
+ """MisakaDB apis implementation."""
misakadb/apis/api.py ADDED
@@ -0,0 +1,394 @@
1
+ import json
2
+ import sys
3
+ import threading
4
+ import time
5
+ from typing import Optional, Dict, Any, List, Union
6
+
7
+
8
+ from ..network import sock
9
+
10
+
11
+ class HeartbeatController:
12
+ def __init__(self, owner: "MisakaDBClient", interval: float) -> None:
13
+ self._owner = owner
14
+ self.interval = interval
15
+ self._enabled = threading.Event()
16
+ self._shutdown = threading.Event()
17
+ self._stats_lock = threading.Lock()
18
+ self._thread: Optional[threading.Thread] = None
19
+ self._attempt_count = 0
20
+ self._success_count = 0
21
+ self._failure_count = 0
22
+ self._last_error: Optional[str] = None
23
+ self._last_sent_at: Optional[float] = None
24
+
25
+ def _ensure_thread(self) -> None:
26
+ if self._thread is not None and self._thread.is_alive():
27
+ return
28
+
29
+ self._shutdown.clear()
30
+ self._thread = threading.Thread(
31
+ target=self._loop,
32
+ name=f"MisakaDBHeartbeat-{self._owner.host}:{self._owner.port}",
33
+ daemon=True,
34
+ )
35
+ self._thread.start()
36
+
37
+ def _loop(self) -> None:
38
+ while not self._shutdown.is_set():
39
+ if not self._enabled.is_set():
40
+ if self._shutdown.wait(0.1):
41
+ return
42
+ continue
43
+
44
+ if self._owner.connected and self._owner.client is not None:
45
+ try:
46
+ with self._owner._socket_lock:
47
+ if self._owner.connected and self._owner.client is not None:
48
+ self._owner.client.send_heartbeat()
49
+ with self._stats_lock:
50
+ self._attempt_count += 1
51
+ self._success_count += 1
52
+ self._last_error = None
53
+ self._last_sent_at = time.time()
54
+ except Exception as e:
55
+ with self._stats_lock:
56
+ self._attempt_count += 1
57
+ self._failure_count += 1
58
+ self._last_error = str(e)
59
+ print(f"心跳发送失败: {e}", file=sys.stderr)
60
+ self._owner.connected = False
61
+
62
+ if self._shutdown.wait(self.interval):
63
+ return
64
+
65
+ @property
66
+ def running(self) -> bool:
67
+ return self._enabled.is_set()
68
+
69
+ @property
70
+ def count(self) -> int:
71
+ with self._stats_lock:
72
+ return self._attempt_count
73
+
74
+ @property
75
+ def success_count(self) -> int:
76
+ with self._stats_lock:
77
+ return self._success_count
78
+
79
+ @property
80
+ def failure_count(self) -> int:
81
+ with self._stats_lock:
82
+ return self._failure_count
83
+
84
+ @property
85
+ def loss_rate(self) -> float:
86
+ with self._stats_lock:
87
+ if self._attempt_count == 0:
88
+ return 0.0
89
+ return self._failure_count / self._attempt_count * 100.0
90
+
91
+ @property
92
+ def last_error(self) -> Optional[str]:
93
+ with self._stats_lock:
94
+ return self._last_error
95
+
96
+ @property
97
+ def last_sent_at(self) -> Optional[float]:
98
+ with self._stats_lock:
99
+ return self._last_sent_at
100
+
101
+ def stats(self) -> Dict[str, Any]:
102
+ with self._stats_lock:
103
+ loss_rate = (
104
+ self._failure_count / self._attempt_count * 100.0
105
+ if self._attempt_count > 0 else 0.0
106
+ )
107
+ return {
108
+ "running": self._enabled.is_set(),
109
+ "interval": self.interval,
110
+ "count": self._attempt_count,
111
+ "success_count": self._success_count,
112
+ "failure_count": self._failure_count,
113
+ # 当前协议没有心跳回包确认,这里按发送失败率统计。
114
+ "loss_rate": loss_rate,
115
+ "last_error": self._last_error,
116
+ "last_sent_at": self._last_sent_at,
117
+ }
118
+
119
+ def start(self) -> None:
120
+ self._ensure_thread()
121
+ self._enabled.set()
122
+
123
+ def stop(self) -> None:
124
+ self._enabled.clear()
125
+
126
+ def shutdown(self) -> None:
127
+ self._enabled.clear()
128
+ self._shutdown.set()
129
+
130
+
131
+ class MisakaDBClient:
132
+ """MisakaDB客户端API"""
133
+
134
+ def __init__(
135
+ self,
136
+ host: str = "127.0.0.1",
137
+ port: int = 10032,
138
+ heartbeat_interval: float = 1
139
+ ):
140
+ self.host = host
141
+ self.port = port
142
+ self.client: Optional[sock.clientCore] = None
143
+ self.service_info: Optional[Dict[str, Any]] = None
144
+ self.connected = False
145
+ self.heartbeat_interval = heartbeat_interval
146
+ self._socket_lock = threading.Lock()
147
+ self.heart = HeartbeatController(self, heartbeat_interval)
148
+ self.heart.start()
149
+
150
+ def connect(self, retries: int = 3, retry_delay: float = 1.0) -> bool:
151
+ """连接到MisakaDB服务器
152
+
153
+ Args:
154
+ retries: 重试次数
155
+ retry_delay: 重试延迟(秒)
156
+
157
+ Returns:
158
+ bool: 连接是否成功
159
+ """
160
+ self.heart.start()
161
+
162
+ for attempt in range(retries):
163
+ try:
164
+ self.client = sock.clientCore(self.host, self.port)
165
+ connect_err = self.client.connect()
166
+
167
+ if connect_err is not None:
168
+ if attempt < retries - 1:
169
+ print(f"连接失败,第{attempt + 1}次重试... ({connect_err})", file=sys.stderr)
170
+ time.sleep(retry_delay)
171
+ continue
172
+ else:
173
+ print(f"连接失败: {connect_err}", file=sys.stderr)
174
+ return False
175
+
176
+ self.connected = True
177
+ return True
178
+
179
+ except Exception as e:
180
+ if attempt < retries - 1:
181
+ print(f"连接过程中发生错误,第{attempt + 1}次重试... ({e})", file=sys.stderr)
182
+ time.sleep(retry_delay)
183
+ else:
184
+ print(f"连接过程中发生错误: {e}", file=sys.stderr)
185
+ return False
186
+ return False
187
+
188
+ def get_service_info(self) -> Optional[Dict[str, Any]]:
189
+ """获取服务器信息
190
+
191
+ Returns:
192
+ Optional[Dict[str, Any]]: 服务信息字典,如果失败返回None
193
+ """
194
+ if not self.connected or self.client is None:
195
+ print("未连接到服务器,请先调用connect()", file=sys.stderr)
196
+ return None
197
+
198
+ try:
199
+ with self._socket_lock:
200
+ if not self.connected or self.client is None:
201
+ return None
202
+ response = self.client.send_command("get-service-info")
203
+ self.service_info = json.loads(response)
204
+ return self.service_info
205
+ except json.JSONDecodeError as e:
206
+ print(f"解析服务信息失败: {e}", file=sys.stderr)
207
+ return None
208
+ except Exception as e:
209
+ print(f"获取服务信息失败: {e}", file=sys.stderr)
210
+ return None
211
+
212
+ def execute_command(self, command: str) -> Optional[Union[str, Dict, List]]:
213
+ """执行自定义命令
214
+
215
+ Args:
216
+ command: 要执行的命令字符串
217
+
218
+ Returns:
219
+ Optional[Union[str, Dict, List]]: 命令执行结果
220
+ """
221
+ if not self.connected or self.client is None:
222
+ print("未连接到服务器,请先调用connect()", file=sys.stderr)
223
+ return None
224
+
225
+ try:
226
+ with self._socket_lock:
227
+ if not self.connected or self.client is None:
228
+ return None
229
+ response = self.client.send_command(command)
230
+
231
+ # 尝试解析为JSON
232
+ try:
233
+ return json.loads(response)
234
+ except json.JSONDecodeError:
235
+ # 如果不是JSON,返回原始字符串
236
+ return response.decode('utf-8', errors='replace')
237
+
238
+ except Exception as e:
239
+ print(f"执行命令失败: {e}", file=sys.stderr)
240
+ return None
241
+
242
+ def ping(self) -> bool:
243
+ """检查服务器是否可达
244
+
245
+ Returns:
246
+ bool: 服务器是否可达
247
+ """
248
+ if not self.connected or self.client is None:
249
+ return False
250
+
251
+ try:
252
+ with self._socket_lock:
253
+ if not self.connected or self.client is None:
254
+ return False
255
+ response = self.client.send_command("ping")
256
+ return response is not None
257
+ except:
258
+ return False
259
+
260
+ def get_server_version(self) -> Optional[str]:
261
+ """获取服务器版本
262
+
263
+ Returns:
264
+ Optional[str]: 服务器版本号
265
+ """
266
+ service_info = self.get_service_info()
267
+ if service_info:
268
+ return service_info.get('service', {}).get('version')
269
+ return None
270
+
271
+
272
+
273
+ def is_command_allowed(self, command: str) -> bool:
274
+ """检查命令是否被服务器允许
275
+
276
+ Args:
277
+ command: 要检查的命令
278
+
279
+ Returns:
280
+ bool: 命令是否被允许
281
+ """
282
+ allowed_commands = self.get_allowed_commands()
283
+ if allowed_commands:
284
+ return command in allowed_commands
285
+ return False
286
+
287
+ def get_network_config(self) -> Optional[Dict[str, Any]]:
288
+ """获取网络配置
289
+
290
+ Returns:
291
+ Optional[Dict[str, Any]]: 网络配置信息
292
+ """
293
+ service_info = self.get_service_info()
294
+ if service_info:
295
+ return service_info.get('network', {})
296
+ return None
297
+
298
+ def close(self) -> None:
299
+ """关闭连接"""
300
+ self.heart.shutdown()
301
+ with self._socket_lock:
302
+ if self.client is not None and self.client.s:
303
+ self.client.close()
304
+ self.client = None
305
+ self.service_info = None
306
+ self.connected = False
307
+ self.heart = HeartbeatController(self, self.heartbeat_interval)
308
+
309
+ def __enter__(self):
310
+ """上下文管理器入口"""
311
+ if not self.connect():
312
+ raise ConnectionError(f"无法连接到服务器 {self.host}:{self.port}")
313
+ return self
314
+
315
+ def __exit__(self, exc_type, exc_val, exc_tb):
316
+ """上下文管理器退出"""
317
+ self.close()
318
+
319
+ def __repr__(self) -> str:
320
+ """对象表示"""
321
+ status = "已连接" if self.connected else "未连接"
322
+ return f"<MisakaDBClient {self.host}:{self.port} [{status}]>"
323
+
324
+
325
+ # 兼容旧版本的函数接口
326
+ def connect(host: str, port: int) -> Optional[MisakaDBClient]:
327
+ """连接到MisakaDB服务器(兼容旧版本接口)
328
+
329
+ Args:
330
+ host: 服务器地址
331
+ port: 服务器端口
332
+
333
+ Returns:
334
+ Optional[MisakaDBClient]: 客户端实例,如果连接失败返回None
335
+ """
336
+ client = MisakaDBClient(host, port)
337
+ if client.connect():
338
+ return client
339
+ return None
340
+
341
+
342
+ # 示例使用
343
+ if __name__ == "__main__":
344
+ # 使用上下文管理器(推荐)
345
+ print("=== 使用上下文管理器 ===")
346
+ try:
347
+ with MisakaDBClient("127.0.0.1", 10032) as client:
348
+ # 获取服务信息
349
+ service_info = client.get_service_info()
350
+ if service_info:
351
+ print("服务信息获取成功:")
352
+ print(json.dumps(service_info, indent=2, ensure_ascii=False))
353
+
354
+ # 获取特定信息
355
+ version = client.get_server_version()
356
+ allowed_commands = client.get_allowed_commands()
357
+ network_config = client.get_network_config()
358
+
359
+ print(f"\n服务器版本: {version}")
360
+ print(f"允许的命令: {allowed_commands}")
361
+ print(f"网络配置: {network_config}")
362
+
363
+ # 检查ping
364
+ if client.ping():
365
+ print("服务器可达")
366
+ else:
367
+ print("服务器不可达")
368
+
369
+ # 检查命令是否允许
370
+ test_command = "get-service-info"
371
+ if client.is_command_allowed(test_command):
372
+ print(f"命令 '{test_command}' 被允许")
373
+ else:
374
+ print(f"命令 '{test_command}' 不被允许")
375
+
376
+ # 执行自定义命令
377
+ result = client.execute_command("get-service-info")
378
+ print(f"\n执行命令结果: {result}")
379
+ except ConnectionError as e:
380
+ print(f"连接失败: {e}")
381
+
382
+ print("\n=== 使用传统方式 ===")
383
+ client = MisakaDBClient("127.0.0.1", 10032)
384
+ if client.connect():
385
+ try:
386
+ service_info = client.get_service_info()
387
+ if service_info:
388
+ print(f"服务版本: {service_info.get('service', {}).get('version', '未知')}")
389
+ print(f"监听地址: {service_info.get('network', {}).get('address', '未知')}")
390
+ print(f"监听端口: {service_info.get('network', {}).get('port', '未知')}")
391
+ finally:
392
+ client.close()
393
+ else:
394
+ print("连接失败")
misakadb/cli.py ADDED
@@ -0,0 +1,124 @@
1
+ import argparse
2
+ import getpass
3
+ import os
4
+ import sys
5
+ import time
6
+
7
+ # 在类 Unix 终端中启用 input() 的行编辑和历史能力。
8
+ try:
9
+ import readline
10
+ readline.parse_and_bind("tab: complete")
11
+ except ImportError:
12
+ readline = None
13
+
14
+ from .interface.status import StatusSocket
15
+ from . import __version__
16
+ from .mql.MQ import MiQL
17
+ from .network.command_send import commandSend
18
+ from .network import sock
19
+
20
+
21
+ def main(argv=None):
22
+ parser = argparse.ArgumentParser(description=f"MisakaDB CLI {__version__}")
23
+ parser.add_argument("--version", action="version", version=f"misakadb {__version__}")
24
+ parser.add_argument("--address", type=str, default="127.0.0.1", help="服务地址, 默认本地")
25
+ parser.add_argument("--port", type=int, default=10032, help="服务端口, 默认10032")
26
+ parser.add_argument("--mode", type=str, default="shell", help="运行模式: shell[cli调试模式]、onlyConn[仅连接]")
27
+ parser.add_argument("--username", type=str, default="", help="[可选]用户名")
28
+ parser.add_argument("--password", type=str, default="", help="[可选]密码")
29
+
30
+
31
+ args = parser.parse_args(argv)
32
+
33
+ address = args.address
34
+ port = args.port
35
+ mode = args.mode
36
+ username = args.username
37
+ password = args.password
38
+
39
+
40
+ cli = sock.clientCore(address, port)
41
+ connect_err = cli.connect()
42
+ if connect_err is not None:
43
+ print(f"连接失败: {connect_err}", file=sys.stderr)
44
+ cli.close()
45
+ sys.exit(1)
46
+
47
+ try:
48
+ command_send = commandSend(cli)
49
+ server_recv = command_send.init_command()
50
+
51
+ if mode in ["shell", "sh", "s"]:
52
+ retry = False
53
+ while username == "" or password == "":
54
+ if retry:
55
+ print("\n登录失败, 请重新输入用户名和密码:\n")
56
+ else:
57
+ retry = True
58
+ username = input("请输入用户名: ")
59
+ password = getpass.getpass("请输入密码: ")
60
+
61
+
62
+
63
+ while not command_send.login(username, password):
64
+ print("登录失败, 请重新输入用户名和密码:\n")
65
+ username = input("请输入用户名: ")
66
+ password = getpass.getpass("请输入密码: ")
67
+
68
+ cli.status = StatusSocket.Connected
69
+
70
+
71
+ mq = MiQL(cli)
72
+ while True:
73
+ commands = ""
74
+
75
+
76
+ user_input = "\\"
77
+ row = 0
78
+ while user_input.endswith("\\"):
79
+ selection = ""
80
+ if cli.session.database:
81
+ selection = f" db={cli.session.database}"
82
+ if cli.session.table:
83
+ selection += f" table={cli.session.table}"
84
+ user_input = input(f"[{address}:{port} {cli.status.value}{selection}]{row if row > 0 else ''} >")
85
+ commands += (user_input[:-1] if user_input.endswith("\\") else user_input) + "\n"
86
+ row += 1
87
+
88
+ commands = commands.strip()
89
+ if commands.startswith("mq."):
90
+ try:
91
+ start_time = time.time()
92
+ res = mq.shot(commands)
93
+ end_time = time.time()
94
+ elapsed_ms = (end_time - start_time) * 1000.0
95
+
96
+ if isinstance(res, bytes):
97
+ res_str = res.decode("utf-8", errors="replace")
98
+ command_send.handle_response(res_str, elapsed_ms, is_init=False)
99
+ elif res is not None:
100
+ print(res)
101
+ except Exception as e:
102
+ print(f"MiQL 执行失败: {e}", file=sys.stderr)
103
+ continue
104
+
105
+ elif commands in ["exit", 'e', 'q']:
106
+ cli.close()
107
+ print("bye~")
108
+ sys.exit(0)
109
+ elif commands:
110
+ start_time = time.time()
111
+ res = cli.send_command(commands)
112
+ end_time = time.time()
113
+ elapsed_ms = (end_time - start_time) * 1000.0
114
+
115
+ res_str = res.decode('utf-8', errors='replace')
116
+ command_send.handle_response(res_str, elapsed_ms, is_init=False)
117
+ except (EOFError, KeyboardInterrupt):
118
+ print("\nbye~")
119
+ finally:
120
+ cli.close()
121
+
122
+
123
+ if __name__ == "__main__":
124
+ main()
@@ -0,0 +1 @@
1
+ """MisakaDB interface implementation."""
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class StatusSocket(Enum):
5
+ Disconnected = "Disconnected"
6
+ ConnectedNoAuth = "Connected(NoAuth)"
7
+ Connected = "Connected(Auth)"