mhuixs 0.2.3__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.
mhuixs-0.2.3/PKG-INFO ADDED
@@ -0,0 +1,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: mhuixs
3
+ Version: 0.2.3
4
+ Summary: Mhuixs 内存数据结构服务端的 Python 客户端(MHP/1 协议)
5
+ Author: HuJi
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/hujiyo/Mhuixs
8
+ Project-URL: Repository, https://github.com/hujiyo/Mhuixs
9
+ Project-URL: Issues, https://github.com/hujiyo/Mhuixs/issues
10
+ Keywords: mhuixs,in-memory,database,client,redis-like
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Database
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ Provides-Extra: dev
21
+ Requires-Dist: build>=1.2; extra == "dev"
22
+ Requires-Dist: twine>=5.0; extra == "dev"
23
+
24
+ # mhuixs
25
+
26
+ Mhuixs 内存数据结构服务端的 Python 客户端。协议是 MHP/1 —— 请求带长度前缀的
27
+ 参数数组,响应是带长度前缀的 JSON 文档,二进制安全。
28
+
29
+ 包内只有标准库依赖,Python 3.8+。
30
+
31
+ ## 安装
32
+
33
+ ```bash
34
+ pip install mhuixs
35
+ ```
36
+
37
+ 从源码安装:
38
+
39
+ ```bash
40
+ pip install ./client/python
41
+ # 或从仓库直接装:
42
+ pip install "git+https://github.com/hujiyo/Mhuixs.git#subdirectory=client/python"
43
+ ```
44
+
45
+ ## 快速开始
46
+
47
+ 先起服务端(令牌来自环境变量或 `Mhuixs.config` 的 `authtoken`):
48
+
49
+ ```bash
50
+ MHUIXS_TOKEN=你的令牌 ./mhuixs server
51
+ ```
52
+
53
+ 然后在 Python 里:
54
+
55
+ ```python
56
+ import mhuixs
57
+
58
+ db = mhuixs.Mhuixs("127.0.0.1", 18185, token="你的令牌")
59
+
60
+ db.create_list("todo")
61
+ db.rpush("todo", "milk", 2) # 字符串存字符串,数字存数字
62
+ db.llen("todo") # -> 2
63
+ db.lget("todo", 0) # -> "milk"
64
+ db.lget("todo", 1) # -> 2
65
+
66
+ db.create_table("users", ["id:int", "name:str"])
67
+ db.tadd("users", 1, "张三")
68
+ db.tget("users", 0, "name") # -> "张三"
69
+
70
+ db.create_kvalot("conf")
71
+ db.kset("conf", "theme", "dark")
72
+ db.kget("conf", "theme") # -> "dark"
73
+
74
+ db.create_bitmap("flags")
75
+ db.bset("flags", 3, 1) # -> True
76
+ db.bcount("flags", 0, 7) # -> 1
77
+
78
+ db.save() # 让服务端落盘
79
+ db.close()
80
+ ```
81
+
82
+ 也可以当上下文管理器:
83
+
84
+ ```python
85
+ with mhuixs.Mhuixs(token="你的令牌") as db:
86
+ print(db.hooks())
87
+ ```
88
+
89
+ ## 方法一览
90
+
91
+ | 分组 | 方法 | 返回 |
92
+ |---|---|---|
93
+ | 管理 | `create_list` / `create_bitmap` / `create_kvalot` / `create_table` | `None` |
94
+ | 管理 | `drop` | `None` |
95
+ | 管理 | `hooks` | `int` |
96
+ | 管理 | `info` / `type` | `str` |
97
+ | LIST | `rpush` / `lpush` | `int`(新长度) |
98
+ | LIST | `lpop` / `rpop` / `lget` / `lrem` | 值 |
99
+ | LIST | `llen` | `int` |
100
+ | LIST | `lset` | `None` |
101
+ | BITMAP | `bset` / `bget` | `bool` |
102
+ | BITMAP | `bcount` / `bsize` | `int` |
103
+ | KVALOT | `kset` / `kdel` / `klen` | `int` |
104
+ | KVALOT | `kget` | 值 |
105
+ | KVALOT | `kexists` | `bool` |
106
+ | TABLE | `tadd` / `tdel` / `trows` | `int` |
107
+ | TABLE | `tget` | 值或 `None`(空格子) |
108
+ | TABLE | `tset` | `None` |
109
+ | TABLE | `tfields` | `list[str]` |
110
+ | 会话 | `ping` | `bool` |
111
+ | 会话 | `save` / `shutdown` | `str` |
112
+
113
+ `tfields` 受服务端类型化返回值 32 项上限约束;字段更多时用 `call()` 看
114
+ `truncated`,或读 `info()` 的文本。
115
+
116
+ ## 参数类型规则
117
+
118
+ 类型化方法按 Python 类型决定值怎么存:
119
+
120
+ | Python 值 | 存进去是什么 |
121
+ |---|---|
122
+ | `str` / `bytes` | 字符串(含换行的值也能原样存) |
123
+ | `int` | 数字 |
124
+ | `float` / `Decimal` | 数字(按十进制定点写入,不使用科学计数法) |
125
+ | `bool` | 数字 `1` / `0` |
126
+ | `None` | 不支持,抛 `ValueError` / `TypeError` |
127
+
128
+ 需要把数字样子的字符串强制存成字符串时,不需要额外包装 —— 类型化方法里的
129
+ `str` 本来就是字符串:
130
+
131
+ ```python
132
+ db.rpush("numbers", "123") # 字符串 "123"
133
+ db.rpush("numbers", 123) # 数字 123
134
+ ```
135
+
136
+ 裸接口 `call()` / `execute()` 不做这层包装:普通 `str` 交给服务端自动判定
137
+ (`"123"` 会变数字),要强制字符串时用 `mhuixs.Forced`:
138
+
139
+ ```python
140
+ db.execute("rpush", "numbers", mhuixs.Forced("123")) # 字符串
141
+ ```
142
+
143
+ 服务端单个参数上限 255 字节、单条命令 48 个参数;客户端会在发送前检查并给出
144
+ 明确错误。参数里不能有 NUL 字节,`Forced` 的值不能含 CR/LF(协议限制)。
145
+
146
+ ## 返回值类型
147
+
148
+ | 服务端 kind | Python 类型 |
149
+ |---|---|
150
+ | `int` | `int` |
151
+ | `num` 无小数点 | `int` |
152
+ | `num` 有小数点 | `decimal.Decimal` |
153
+ | `str` | `str` |
154
+ | `bool` | `bool` |
155
+ | `null` | `None` |
156
+ | `list` | `list` |
157
+
158
+ 小数用 `Decimal` 是为了不丢精度。`call()` 返回的是原始 JSON 文档,小数按标准
159
+ JSON 解析成 `float`;需要无损数值时用 `execute()` 或类型化方法。
160
+
161
+ 服务端返回的非 UTF-8 字节会以 `surrogateescape` 形式进入 Python 字符串,
162
+ 用 `value.encode("utf-8", "surrogateescape")` 可以还原原始字节。
163
+
164
+ ## 原始接口
165
+
166
+ ```python
167
+ db.call("llen", "todo")
168
+ # {'ok': True, 'code': 'ok', 'kind': 'int', 'value': 2, 'text': 'OK 2'}
169
+
170
+ db.execute("llen", "todo") # -> 2
171
+ ```
172
+
173
+ - `call()` 永远返回完整 dict;服务端业务失败(`ok: false`)不抛异常。
174
+ - `execute()` 在 `ok: false` 时抛对应异常,否则返回值。
175
+
176
+ ## 异常
177
+
178
+ 所有异常都继承 `mhuixs.MhuixsError`。服务端失败响应按稳定的 `code` 映射:
179
+
180
+ | code | 异常 |
181
+ |---|---|
182
+ | `bad_usage` | `BadUsageError` |
183
+ | `no_hook` | `NoHookError` |
184
+ | `no_object` | `NoObjectError` |
185
+ | `type_mismatch` | `TypeMismatchError` |
186
+ | `out_of_range` | `OutOfRangeError` |
187
+ | `no_key` | `NoKeyError` |
188
+ | `no_field` | `NoFieldError` |
189
+ | `empty` | `EmptyError` |
190
+ | `name_taken` | `NameTakenError` |
191
+ | `unknown_command` | `UnknownCommandError` |
192
+ | 其他 | `CommandError` |
193
+
194
+ 连接断开或关闭抛 `ConnectionError`;认证失败抛 `AuthenticationError`;
195
+ 分帧损坏抛 `ProtocolError`。业务异常带 `.code` / `.text` 属性。
196
+
197
+ ## 命令行客户端
198
+
199
+ 安装后会有一个 `mhuixs-cli` 命令;在源码目录 `client/python` 下也可以用
200
+ `python -m mhuixs`,参数完全一样:
201
+
202
+ ```bash
203
+ mhuixs-cli # 交互模式
204
+ mhuixs-cli 'llen todo' # 执行一条命令
205
+ mhuixs-cli - < script.mhx # 从 stdin / 管道逐行读
206
+ python -m mhuixs 'rpush todo milk' # 等价写法
207
+ ```
208
+
209
+ 令牌按 `--token` > 环境变量 `MHUIXS_TOKEN` > `Mhuixs.config` 的 `authtoken`
210
+ 顺序回落。输出版式用 `--format value|pretty|text|json`(默认 `value`)。
211
+ 交互模式里 `:help` 看元命令,`:quit` 退出。
212
+
213
+ ## 线程与连接
214
+
215
+ 一个 `Mhuixs` 实例对应一条连接,内部有锁,多个线程共用一个实例时命令会串行
216
+ 执行。想并行请每个线程各建一个实例。服务端当前一次只服务一个连接,所以不做
217
+ 连接池和自动重连。
218
+
219
+ ## 测试
220
+
221
+ ```bash
222
+ cd client/python
223
+ python -m unittest discover -s tests -v
224
+ ```
225
+
226
+ 测试只用标准库,不依赖真实服务端:协议黄金字节、半包/粘包、认证、错误映射
227
+ 都由本地假服务端覆盖。
228
+
229
+ ## 协议与许可
230
+
231
+ - 线格式规范:[`src/proto.h`](https://github.com/hujiyo/Mhuixs/blob/main/src/proto.h)
232
+ - 项目主页:<https://github.com/hujiyo/Mhuixs>
233
+ - 许可证:Apache License 2.0
mhuixs-0.2.3/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # mhuixs
2
+
3
+ Mhuixs 内存数据结构服务端的 Python 客户端。协议是 MHP/1 —— 请求带长度前缀的
4
+ 参数数组,响应是带长度前缀的 JSON 文档,二进制安全。
5
+
6
+ 包内只有标准库依赖,Python 3.8+。
7
+
8
+ ## 安装
9
+
10
+ ```bash
11
+ pip install mhuixs
12
+ ```
13
+
14
+ 从源码安装:
15
+
16
+ ```bash
17
+ pip install ./client/python
18
+ # 或从仓库直接装:
19
+ pip install "git+https://github.com/hujiyo/Mhuixs.git#subdirectory=client/python"
20
+ ```
21
+
22
+ ## 快速开始
23
+
24
+ 先起服务端(令牌来自环境变量或 `Mhuixs.config` 的 `authtoken`):
25
+
26
+ ```bash
27
+ MHUIXS_TOKEN=你的令牌 ./mhuixs server
28
+ ```
29
+
30
+ 然后在 Python 里:
31
+
32
+ ```python
33
+ import mhuixs
34
+
35
+ db = mhuixs.Mhuixs("127.0.0.1", 18185, token="你的令牌")
36
+
37
+ db.create_list("todo")
38
+ db.rpush("todo", "milk", 2) # 字符串存字符串,数字存数字
39
+ db.llen("todo") # -> 2
40
+ db.lget("todo", 0) # -> "milk"
41
+ db.lget("todo", 1) # -> 2
42
+
43
+ db.create_table("users", ["id:int", "name:str"])
44
+ db.tadd("users", 1, "张三")
45
+ db.tget("users", 0, "name") # -> "张三"
46
+
47
+ db.create_kvalot("conf")
48
+ db.kset("conf", "theme", "dark")
49
+ db.kget("conf", "theme") # -> "dark"
50
+
51
+ db.create_bitmap("flags")
52
+ db.bset("flags", 3, 1) # -> True
53
+ db.bcount("flags", 0, 7) # -> 1
54
+
55
+ db.save() # 让服务端落盘
56
+ db.close()
57
+ ```
58
+
59
+ 也可以当上下文管理器:
60
+
61
+ ```python
62
+ with mhuixs.Mhuixs(token="你的令牌") as db:
63
+ print(db.hooks())
64
+ ```
65
+
66
+ ## 方法一览
67
+
68
+ | 分组 | 方法 | 返回 |
69
+ |---|---|---|
70
+ | 管理 | `create_list` / `create_bitmap` / `create_kvalot` / `create_table` | `None` |
71
+ | 管理 | `drop` | `None` |
72
+ | 管理 | `hooks` | `int` |
73
+ | 管理 | `info` / `type` | `str` |
74
+ | LIST | `rpush` / `lpush` | `int`(新长度) |
75
+ | LIST | `lpop` / `rpop` / `lget` / `lrem` | 值 |
76
+ | LIST | `llen` | `int` |
77
+ | LIST | `lset` | `None` |
78
+ | BITMAP | `bset` / `bget` | `bool` |
79
+ | BITMAP | `bcount` / `bsize` | `int` |
80
+ | KVALOT | `kset` / `kdel` / `klen` | `int` |
81
+ | KVALOT | `kget` | 值 |
82
+ | KVALOT | `kexists` | `bool` |
83
+ | TABLE | `tadd` / `tdel` / `trows` | `int` |
84
+ | TABLE | `tget` | 值或 `None`(空格子) |
85
+ | TABLE | `tset` | `None` |
86
+ | TABLE | `tfields` | `list[str]` |
87
+ | 会话 | `ping` | `bool` |
88
+ | 会话 | `save` / `shutdown` | `str` |
89
+
90
+ `tfields` 受服务端类型化返回值 32 项上限约束;字段更多时用 `call()` 看
91
+ `truncated`,或读 `info()` 的文本。
92
+
93
+ ## 参数类型规则
94
+
95
+ 类型化方法按 Python 类型决定值怎么存:
96
+
97
+ | Python 值 | 存进去是什么 |
98
+ |---|---|
99
+ | `str` / `bytes` | 字符串(含换行的值也能原样存) |
100
+ | `int` | 数字 |
101
+ | `float` / `Decimal` | 数字(按十进制定点写入,不使用科学计数法) |
102
+ | `bool` | 数字 `1` / `0` |
103
+ | `None` | 不支持,抛 `ValueError` / `TypeError` |
104
+
105
+ 需要把数字样子的字符串强制存成字符串时,不需要额外包装 —— 类型化方法里的
106
+ `str` 本来就是字符串:
107
+
108
+ ```python
109
+ db.rpush("numbers", "123") # 字符串 "123"
110
+ db.rpush("numbers", 123) # 数字 123
111
+ ```
112
+
113
+ 裸接口 `call()` / `execute()` 不做这层包装:普通 `str` 交给服务端自动判定
114
+ (`"123"` 会变数字),要强制字符串时用 `mhuixs.Forced`:
115
+
116
+ ```python
117
+ db.execute("rpush", "numbers", mhuixs.Forced("123")) # 字符串
118
+ ```
119
+
120
+ 服务端单个参数上限 255 字节、单条命令 48 个参数;客户端会在发送前检查并给出
121
+ 明确错误。参数里不能有 NUL 字节,`Forced` 的值不能含 CR/LF(协议限制)。
122
+
123
+ ## 返回值类型
124
+
125
+ | 服务端 kind | Python 类型 |
126
+ |---|---|
127
+ | `int` | `int` |
128
+ | `num` 无小数点 | `int` |
129
+ | `num` 有小数点 | `decimal.Decimal` |
130
+ | `str` | `str` |
131
+ | `bool` | `bool` |
132
+ | `null` | `None` |
133
+ | `list` | `list` |
134
+
135
+ 小数用 `Decimal` 是为了不丢精度。`call()` 返回的是原始 JSON 文档,小数按标准
136
+ JSON 解析成 `float`;需要无损数值时用 `execute()` 或类型化方法。
137
+
138
+ 服务端返回的非 UTF-8 字节会以 `surrogateescape` 形式进入 Python 字符串,
139
+ 用 `value.encode("utf-8", "surrogateescape")` 可以还原原始字节。
140
+
141
+ ## 原始接口
142
+
143
+ ```python
144
+ db.call("llen", "todo")
145
+ # {'ok': True, 'code': 'ok', 'kind': 'int', 'value': 2, 'text': 'OK 2'}
146
+
147
+ db.execute("llen", "todo") # -> 2
148
+ ```
149
+
150
+ - `call()` 永远返回完整 dict;服务端业务失败(`ok: false`)不抛异常。
151
+ - `execute()` 在 `ok: false` 时抛对应异常,否则返回值。
152
+
153
+ ## 异常
154
+
155
+ 所有异常都继承 `mhuixs.MhuixsError`。服务端失败响应按稳定的 `code` 映射:
156
+
157
+ | code | 异常 |
158
+ |---|---|
159
+ | `bad_usage` | `BadUsageError` |
160
+ | `no_hook` | `NoHookError` |
161
+ | `no_object` | `NoObjectError` |
162
+ | `type_mismatch` | `TypeMismatchError` |
163
+ | `out_of_range` | `OutOfRangeError` |
164
+ | `no_key` | `NoKeyError` |
165
+ | `no_field` | `NoFieldError` |
166
+ | `empty` | `EmptyError` |
167
+ | `name_taken` | `NameTakenError` |
168
+ | `unknown_command` | `UnknownCommandError` |
169
+ | 其他 | `CommandError` |
170
+
171
+ 连接断开或关闭抛 `ConnectionError`;认证失败抛 `AuthenticationError`;
172
+ 分帧损坏抛 `ProtocolError`。业务异常带 `.code` / `.text` 属性。
173
+
174
+ ## 命令行客户端
175
+
176
+ 安装后会有一个 `mhuixs-cli` 命令;在源码目录 `client/python` 下也可以用
177
+ `python -m mhuixs`,参数完全一样:
178
+
179
+ ```bash
180
+ mhuixs-cli # 交互模式
181
+ mhuixs-cli 'llen todo' # 执行一条命令
182
+ mhuixs-cli - < script.mhx # 从 stdin / 管道逐行读
183
+ python -m mhuixs 'rpush todo milk' # 等价写法
184
+ ```
185
+
186
+ 令牌按 `--token` > 环境变量 `MHUIXS_TOKEN` > `Mhuixs.config` 的 `authtoken`
187
+ 顺序回落。输出版式用 `--format value|pretty|text|json`(默认 `value`)。
188
+ 交互模式里 `:help` 看元命令,`:quit` 退出。
189
+
190
+ ## 线程与连接
191
+
192
+ 一个 `Mhuixs` 实例对应一条连接,内部有锁,多个线程共用一个实例时命令会串行
193
+ 执行。想并行请每个线程各建一个实例。服务端当前一次只服务一个连接,所以不做
194
+ 连接池和自动重连。
195
+
196
+ ## 测试
197
+
198
+ ```bash
199
+ cd client/python
200
+ python -m unittest discover -s tests -v
201
+ ```
202
+
203
+ 测试只用标准库,不依赖真实服务端:协议黄金字节、半包/粘包、认证、错误映射
204
+ 都由本地假服务端覆盖。
205
+
206
+ ## 协议与许可
207
+
208
+ - 线格式规范:[`src/proto.h`](https://github.com/hujiyo/Mhuixs/blob/main/src/proto.h)
209
+ - 项目主页:<https://github.com/hujiyo/Mhuixs>
210
+ - 许可证:Apache License 2.0
@@ -0,0 +1,57 @@
1
+ """Mhuixs 的 Python 客户端。
2
+
3
+ import mhuixs
4
+
5
+ db = mhuixs.Mhuixs("127.0.0.1", 18185, token="你的令牌")
6
+ db.create_list("todo")
7
+ db.rpush("todo", "milk", 2) # 字符串存字符串,数字存数字
8
+ print(db.llen("todo")) # -> 2
9
+ print(db.lget("todo", 0)) # -> "milk"
10
+ db.close()
11
+
12
+ 需要服务端原始 JSON 文档时用 ``db.call(...)``;要解包后的 Python 值用
13
+ ``db.execute(...)`` 或按数据结构分组的方法。协议参考实现见
14
+ ``mhuixs._protocol``。
15
+ """
16
+
17
+ from ._version import __version__
18
+ from ._client import Mhuixs
19
+ from ._errors import (
20
+ AuthenticationError,
21
+ BadUsageError,
22
+ CommandError,
23
+ ConnectionError,
24
+ EmptyError,
25
+ MhuixsError,
26
+ NameTakenError,
27
+ NoFieldError,
28
+ NoHookError,
29
+ NoKeyError,
30
+ NoObjectError,
31
+ OutOfRangeError,
32
+ ProtocolError,
33
+ TypeMismatchError,
34
+ UnknownCommandError,
35
+ )
36
+ from ._protocol import Forced
37
+
38
+ __all__ = [
39
+ "Mhuixs",
40
+ "Forced",
41
+ "MhuixsError",
42
+ "ConnectionError",
43
+ "ProtocolError",
44
+ "AuthenticationError",
45
+ "CommandError",
46
+ "BadUsageError",
47
+ "NoHookError",
48
+ "NoObjectError",
49
+ "TypeMismatchError",
50
+ "OutOfRangeError",
51
+ "NoKeyError",
52
+ "NoFieldError",
53
+ "EmptyError",
54
+ "NameTakenError",
55
+ "UnknownCommandError",
56
+ "__version__",
57
+ ]
@@ -0,0 +1,6 @@
1
+ """``python -m mhuixs`` 等价于 ``mhuixs-cli``。"""
2
+
3
+ from .cli import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()