pulse-mq 0.1.0__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.
Files changed (68) hide show
  1. pulse_mq-0.1.0/.claude/settings.json +5 -0
  2. pulse_mq-0.1.0/.env.example +48 -0
  3. pulse_mq-0.1.0/.gitignore +10 -0
  4. pulse_mq-0.1.0/.idea/.gitignore +10 -0
  5. pulse_mq-0.1.0/.idea/inspectionProfiles/Project_Default.xml +19 -0
  6. pulse_mq-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  7. pulse_mq-0.1.0/.idea/modules.xml +8 -0
  8. pulse_mq-0.1.0/.idea/pulse-mq.iml +10 -0
  9. pulse_mq-0.1.0/.idea/vcs.xml +6 -0
  10. pulse_mq-0.1.0/.idea/workspace.xml +113 -0
  11. pulse_mq-0.1.0/PKG-INFO +182 -0
  12. pulse_mq-0.1.0/README.md +159 -0
  13. pulse_mq-0.1.0/alembic/env.py +37 -0
  14. pulse_mq-0.1.0/alembic/versions/.gitkeep +0 -0
  15. pulse_mq-0.1.0/alembic.ini +36 -0
  16. pulse_mq-0.1.0/docs/superpowers/plans/2026-05-28-pulse-mq.md +3920 -0
  17. pulse_mq-0.1.0/docs/superpowers/specs/2026-05-28-pulse-mq-design.md +524 -0
  18. pulse_mq-0.1.0/pyproject.toml +41 -0
  19. pulse_mq-0.1.0/src/pulse_mq/__init__.py +6 -0
  20. pulse_mq-0.1.0/src/pulse_mq/auth.py +117 -0
  21. pulse_mq-0.1.0/src/pulse_mq/cli.py +90 -0
  22. pulse_mq-0.1.0/src/pulse_mq/client.py +178 -0
  23. pulse_mq-0.1.0/src/pulse_mq/config.py +69 -0
  24. pulse_mq-0.1.0/src/pulse_mq/database.py +40 -0
  25. pulse_mq-0.1.0/src/pulse_mq/models.py +66 -0
  26. pulse_mq-0.1.0/src/pulse_mq/protocol.py +115 -0
  27. pulse_mq-0.1.0/src/pulse_mq/router.py +200 -0
  28. pulse_mq-0.1.0/src/pulse_mq/server.py +93 -0
  29. pulse_mq-0.1.0/src/pulse_mq/stats.py +87 -0
  30. pulse_mq-0.1.0/src/pulse_mq/web/__init__.py +0 -0
  31. pulse_mq-0.1.0/src/pulse_mq/web/app.py +25 -0
  32. pulse_mq-0.1.0/src/pulse_mq/web/deps.py +35 -0
  33. pulse_mq-0.1.0/src/pulse_mq/web/routes/__init__.py +0 -0
  34. pulse_mq-0.1.0/src/pulse_mq/web/routes/auth.py +28 -0
  35. pulse_mq-0.1.0/src/pulse_mq/web/routes/dashboard.py +99 -0
  36. pulse_mq-0.1.0/src/pulse_mq/web/routes/permissions.py +61 -0
  37. pulse_mq-0.1.0/src/pulse_mq/web/routes/topics.py +78 -0
  38. pulse_mq-0.1.0/src/pulse_mq/web/routes/users.py +83 -0
  39. pulse_mq-0.1.0/src/pulse_mq/web/static/app.js +1 -0
  40. pulse_mq-0.1.0/src/pulse_mq/web/static/style.css +84 -0
  41. pulse_mq-0.1.0/src/pulse_mq/web/templates/__init__.py +0 -0
  42. pulse_mq-0.1.0/src/pulse_mq/web/templates/base.html +14 -0
  43. pulse_mq-0.1.0/src/pulse_mq/web/templates/dashboard.html +121 -0
  44. pulse_mq-0.1.0/src/pulse_mq/web/templates/login.html +42 -0
  45. pulse_mq-0.1.0/src/pulse_mq/web/templates/permissions.html +82 -0
  46. pulse_mq-0.1.0/src/pulse_mq/web/templates/topic_detail.html +70 -0
  47. pulse_mq-0.1.0/src/pulse_mq/web/templates/topics.html +82 -0
  48. pulse_mq-0.1.0/src/pulse_mq/web/templates/user_detail.html +58 -0
  49. pulse_mq-0.1.0/src/pulse_mq/web/templates/users.html +96 -0
  50. pulse_mq-0.1.0/tests/__init__.py +0 -0
  51. pulse_mq-0.1.0/tests/conftest.py +18 -0
  52. pulse_mq-0.1.0/tests/test_auth.py +92 -0
  53. pulse_mq-0.1.0/tests/test_client.py +169 -0
  54. pulse_mq-0.1.0/tests/test_config.py +30 -0
  55. pulse_mq-0.1.0/tests/test_integration.py +101 -0
  56. pulse_mq-0.1.0/tests/test_models.py +67 -0
  57. pulse_mq-0.1.0/tests/test_protocol.py +120 -0
  58. pulse_mq-0.1.0/tests/test_router.py +229 -0
  59. pulse_mq-0.1.0/tests/test_server.py +22 -0
  60. pulse_mq-0.1.0/tests/test_stats.py +45 -0
  61. pulse_mq-0.1.0/tests/web/__init__.py +0 -0
  62. pulse_mq-0.1.0/tests/web/conftest.py +45 -0
  63. pulse_mq-0.1.0/tests/web/test_auth_routes.py +30 -0
  64. pulse_mq-0.1.0/tests/web/test_dashboard.py +33 -0
  65. pulse_mq-0.1.0/tests/web/test_permissions.py +34 -0
  66. pulse_mq-0.1.0/tests/web/test_topics.py +31 -0
  67. pulse_mq-0.1.0/tests/web/test_users.py +45 -0
  68. pulse_mq-0.1.0/uv.lock +1510 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "superpowers@superpowers-marketplace": true
4
+ }
5
+ }
@@ -0,0 +1,48 @@
1
+ # ============================================================
2
+ # PulseMQ 服务端配置
3
+ # 将此文件复制为 .env 并根据实际环境修改
4
+ # ============================================================
5
+
6
+ # ---- HTTP 服务 ----
7
+ # 管理后台和 API 的监听地址
8
+ PULSE_HOST=0.0.0.0
9
+ # 管理后台和 API 的端口
10
+ PULSE_HTTP_PORT=8080
11
+
12
+ # ---- ZMQ 消息服务 ----
13
+ # ZMQ ROUTER 端口(客户端连接入口,处理认证、订阅、消息下发)
14
+ PULSE_ZMQ_ROUTER_PORT=5555
15
+ # ZMQ PULL 端口(接收客户端推送的消息)
16
+ PULSE_ZMQ_PULL_PORT=5556
17
+
18
+ # ---- 数据库 ----
19
+ # 数据库类型:sqlite 或 mysql
20
+ PULSE_DB_TYPE=sqlite
21
+ # SQLite 文件路径(仅 sqlite 模式)
22
+ PULSE_DB_PATH=./pulse_mq.db
23
+ # MySQL 连接 URL(仅 mysql 模式,格式:mysql+pymysql://user:password@host:port/database)
24
+ # PULSE_DB_URL=mysql+pymysql://root:password@localhost:3306/pulse_mq
25
+
26
+ # ---- 消息队列 ----
27
+ # 每个 topic 在内存中保留的最大消息条数(超出后丢弃最旧消息)
28
+ PULSE_QUEUE_SIZE=1000
29
+
30
+ # ---- 认证 ----
31
+ # 客户端 auth 缓存从数据库刷新的间隔(秒)
32
+ PULSE_AUTH_CACHE_TTL=60
33
+ # JWT 签名密钥(留空则首次启动自动生成)
34
+ PULSE_JWT_SECRET=
35
+ # JWT Token 过期时间(小时)
36
+ PULSE_JWT_EXPIRE_HOURS=24
37
+ # admin 初始密码(留空则首次启动自动生成,输出到控制台和 admin_credentials.txt)
38
+ PULSE_ADMIN_PASSWORD=
39
+
40
+ # ---- 心跳 ----
41
+ # 客户端心跳发送间隔(秒)
42
+ PULSE_HEARTBEAT_INTERVAL=30
43
+ # 服务端判定客户端断线的超时时间(秒)
44
+ PULSE_HEARTBEAT_TIMEOUT=60
45
+
46
+ # ---- 统计 ----
47
+ # 消息统计数据保留天数(超过此天数的每分钟统计数据自动清理)
48
+ PULSE_STATS_RETENTION_DAYS=7
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .env
8
+ *.db
9
+ admin_credentials.txt
10
+ .superpowers/
@@ -0,0 +1,10 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
6
+ # Ignored default folder with query files
7
+ /queries/
8
+ # Datasource local storage ignored files
9
+ /dataSources/
10
+ /dataSources.local.xml
@@ -0,0 +1,19 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
5
+ <option name="ignoredPackages">
6
+ <list>
7
+ <option value="pydantic" />
8
+ <option value="pandas" />
9
+ <option value="numpy" />
10
+ <option value="requests" />
11
+ <option value="python-dateutil" />
12
+ <option value="loguru" />
13
+ <option value="duckdb" />
14
+ <option value="python-dotenv" />
15
+ </list>
16
+ </option>
17
+ </inspection_tool>
18
+ </profile>
19
+ </component>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/pulse-mq.iml" filepath="$PROJECT_DIR$/.idea/pulse-mq.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+ </content>
7
+ <orderEntry type="inheritedJdk" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,113 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="ALL" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="6d42cb57-a997-45ab-99f6-b975145f6d31" name="Changes" comment="" />
8
+ <option name="SHOW_DIALOG" value="false" />
9
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
11
+ <option name="LAST_RESOLUTION" value="IGNORE" />
12
+ </component>
13
+ <component name="Git.Settings">
14
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
15
+ </component>
16
+ <component name="ProjectColorInfo"><![CDATA[{
17
+ "associatedIndex": 8,
18
+ "fromUser": false
19
+ }]]></component>
20
+ <component name="ProjectId" id="3ELrM5vPLVXDlgl9JT4LYp5Ekw5" />
21
+ <component name="ProjectViewState">
22
+ <option name="hideEmptyMiddlePackages" value="true" />
23
+ <option name="showLibraryContents" value="true" />
24
+ </component>
25
+ <component name="PropertiesComponent"><![CDATA[{
26
+ "keyToString": {
27
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
28
+ "RunOnceActivity.ShowReadmeOnStart": "true",
29
+ "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
30
+ "RunOnceActivity.typescript.service.memoryLimit.init": "true",
31
+ "codeWithMe.voiceChat.enabledByDefault": "false",
32
+ "git-widget-placeholder": "master",
33
+ "last_opened_file_path": "D:/Workspace/pulse-mq",
34
+ "node.js.detected.package.eslint": "true",
35
+ "node.js.detected.package.tslint": "true",
36
+ "node.js.selected.package.eslint": "(autodetect)",
37
+ "node.js.selected.package.tslint": "(autodetect)",
38
+ "nodejs_package_manager_path": "npm",
39
+ "vue.rearranger.settings.migration": "true"
40
+ }
41
+ }]]></component>
42
+ <component name="RunManager">
43
+ <configuration name="main" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true">
44
+ <module name="pulse-mq" />
45
+ <option name="ENV_FILES" value="" />
46
+ <option name="INTERPRETER_OPTIONS" value="" />
47
+ <option name="PARENT_ENVS" value="true" />
48
+ <envs>
49
+ <env name="PYTHONUNBUFFERED" value="1" />
50
+ </envs>
51
+ <option name="SDK_HOME" value="" />
52
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
53
+ <option name="IS_MODULE_SDK" value="true" />
54
+ <option name="ADD_CONTENT_ROOTS" value="true" />
55
+ <option name="ADD_SOURCE_ROOTS" value="true" />
56
+ <option name="DEBUG_JUST_MY_CODE" value="false" />
57
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
58
+ <option name="RUN_TOOL" value="" />
59
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/main.py" />
60
+ <option name="PARAMETERS" value="" />
61
+ <option name="SHOW_COMMAND_LINE" value="false" />
62
+ <option name="EMULATE_TERMINAL" value="false" />
63
+ <option name="MODULE_MODE" value="false" />
64
+ <option name="REDIRECT_INPUT" value="false" />
65
+ <option name="INPUT_FILE" value="" />
66
+ <method v="2" />
67
+ </configuration>
68
+ </component>
69
+ <component name="SharedIndexes">
70
+ <attachedChunks>
71
+ <set>
72
+ <option value="bundled-js-predefined-d6986cc7102b-31caf2ab9e3c-JavaScript-PY-261.23567.174" />
73
+ <option value="bundled-python-sdk-024ed6589d45-c2ffad84badb-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-261.23567.174" />
74
+ </set>
75
+ </attachedChunks>
76
+ </component>
77
+ <component name="TaskManager">
78
+ <task active="true" id="Default" summary="Default task">
79
+ <changelist id="6d42cb57-a997-45ab-99f6-b975145f6d31" name="Changes" comment="" />
80
+ <created>1779968640985</created>
81
+ <option name="number" value="Default" />
82
+ <option name="presentableId" value="Default" />
83
+ <updated>1779968640985</updated>
84
+ <workItem from="1779968642081" duration="6531000" />
85
+ </task>
86
+ <servers />
87
+ </component>
88
+ <component name="TypeScriptGeneratedFilesManager">
89
+ <option name="version" value="3" />
90
+ </component>
91
+ <component name="Vcs.Log.Tabs.Properties">
92
+ <option name="TAB_STATES">
93
+ <map>
94
+ <entry key="MAIN">
95
+ <value>
96
+ <State />
97
+ </value>
98
+ </entry>
99
+ </map>
100
+ </option>
101
+ </component>
102
+ <component name="XDebuggerManager">
103
+ <breakpoint-manager>
104
+ <breakpoints>
105
+ <line-breakpoint enabled="true" suspend="THREAD" type="python-line">
106
+ <url>file://$PROJECT_DIR$/main.py</url>
107
+ <line>8</line>
108
+ <option name="timeStamp" value="1" />
109
+ </line-breakpoint>
110
+ </breakpoints>
111
+ </breakpoint-manager>
112
+ </component>
113
+ </project>
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.4
2
+ Name: pulse-mq
3
+ Version: 0.1.0
4
+ Summary: 基于 ZeroMQ 的轻量级消息队列系统
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: alembic>=1.13.0
7
+ Requires-Dist: fastapi>=0.104.0
8
+ Requires-Dist: jinja2>=3.1.0
9
+ Requires-Dist: msgpack>=1.0.0
10
+ Requires-Dist: passlib[bcrypt]>=1.7.4
11
+ Requires-Dist: pyarrow>=14.0.0
12
+ Requires-Dist: pyjwt>=2.8.0
13
+ Requires-Dist: python-dotenv>=1.0.0
14
+ Requires-Dist: python-multipart>=0.0.6
15
+ Requires-Dist: pyzmq>=25.0.0
16
+ Requires-Dist: sqlalchemy>=2.0.0
17
+ Requires-Dist: uvicorn[standard]>=0.24.0
18
+ Provides-Extra: dev
19
+ Requires-Dist: httpx>=0.25.0; extra == 'dev'
20
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
21
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # PulseMQ
25
+
26
+ 基于 ZeroMQ 的轻量级消息队列系统,配套 Grafana 风格的 Web 管理后台。
27
+
28
+ ## 特性
29
+
30
+ - **双 Socket 架构** — ROUTER + PULL,消息延迟低
31
+ - **多格式支持** — 字符串、msgpack、PyArrow
32
+ - **Web 管理后台** — Grafana 暗色主题,ECharts 监控图表
33
+ - **权限管理** — 用户 token 认证,按 topic 控制推送/订阅权限
34
+ - **单进程部署** — 一条命令启动全部服务
35
+ - **PyPI 安装** — `pip install pulse-mq` 即可使用
36
+
37
+ ## 快速安装
38
+
39
+ ```bash
40
+ pip install pulse-mq
41
+ ```
42
+
43
+ ## 快速开始
44
+
45
+ ### 启动服务端
46
+
47
+ ```bash
48
+ # 复制配置文件
49
+ cp .env.example .env
50
+
51
+ # 启动服务
52
+ pulse-mq-server
53
+ ```
54
+
55
+ 首次启动会自动生成 admin 密码,输出到控制台和 `admin_credentials.txt`。
56
+
57
+ 管理后台默认地址: `http://localhost:8080`
58
+
59
+ ### 客户端使用
60
+
61
+ ```python
62
+ from pulse_mq import PulseMQClient
63
+
64
+ client = PulseMQClient(
65
+ host="localhost",
66
+ port=5555,
67
+ username="your-username",
68
+ token="your-token",
69
+ )
70
+
71
+ # 订阅消息(回调方式)
72
+ def on_message(msg):
73
+ print(f"Topic: {msg.topic}, Data: {msg.data}, Format: {msg.format}")
74
+
75
+ client.subscribe("metrics/cpu", callback=on_message)
76
+
77
+ # 推送消息
78
+ client.publish("metrics/cpu", data=42.5, format="string")
79
+
80
+ # 运行(阻塞,处理心跳和消息接收)
81
+ client.run()
82
+ ```
83
+
84
+ ### 服务端 SDK
85
+
86
+ ```python
87
+ from pulse_mq import PulseMQServer
88
+
89
+ server = PulseMQServer() # 自动读取 .env 配置
90
+ server.start()
91
+
92
+ # 服务端直接推送消息
93
+ server.publish("alerts/system", data="CPU 过高", format="string")
94
+ server.publish("metrics/batch", data=df, format="pyarrow") # DataFrame
95
+
96
+ server.stop()
97
+ ```
98
+
99
+ ## 消息格式
100
+
101
+ | 格式 | 说明 | 示例 |
102
+ |------|------|------|
103
+ | `string` | 原生字符串,最长 4096 字节 | `client.publish("t/d", data="hello", format="string")` |
104
+ | `msgpack` | MessagePack 序列化 | `client.publish("t/d", data={"key": "val"}, format="msgpack")` |
105
+ | `pyarrow` | PyArrow IPC 格式(支持 DataFrame) | `client.publish("t/d", data=df, format="pyarrow")` |
106
+
107
+ ## Topic 命名规则
108
+
109
+ 格式: `group/topic`(强制一层层级)
110
+
111
+ - 允许字符: `a-z A-Z 0-9 _ - .`
112
+ - 每段长度: 1-128 字符
113
+ - 示例: `metrics/cpu`, `logs/app-error`, `data_1/test.topic`
114
+ - 客户端推送/订阅时,topic 不存在会自动创建
115
+
116
+ ## 配置项
117
+
118
+ 所有配置通过 `.env` 文件或环境变量设置:
119
+
120
+ | 环境变量 | 默认值 | 说明 |
121
+ |---------|--------|------|
122
+ | `PULSE_HOST` | `0.0.0.0` | HTTP 服务监听地址 |
123
+ | `PULSE_HTTP_PORT` | `8080` | HTTP 服务端口 |
124
+ | `PULSE_ZMQ_ROUTER_PORT` | `5555` | ZMQ ROUTER 端口 |
125
+ | `PULSE_ZMQ_PULL_PORT` | `5556` | ZMQ PULL 端口 |
126
+ | `PULSE_DB_TYPE` | `sqlite` | 数据库类型 (sqlite/mysql) |
127
+ | `PULSE_DB_PATH` | `./pulse_mq.db` | SQLite 文件路径 |
128
+ | `PULSE_DB_URL` | - | MySQL 连接 URL |
129
+ | `PULSE_QUEUE_SIZE` | `1000` | 每 topic 内存队列大小 |
130
+ | `PULSE_AUTH_CACHE_TTL` | `60` | auth 缓存刷新间隔(秒) |
131
+ | `PULSE_JWT_SECRET` | 自动生成 | JWT 签名密钥 |
132
+ | `PULSE_JWT_EXPIRE_HOURS` | `24` | JWT 过期时间(小时) |
133
+ | `PULSE_ADMIN_PASSWORD` | 自动生成 | admin 初始密码 |
134
+ | `PULSE_HEARTBEAT_INTERVAL` | `30` | 客户端心跳间隔(秒) |
135
+ | `PULSE_HEARTBEAT_TIMEOUT` | `60` | 服务端断线超时(秒) |
136
+ | `PULSE_STATS_RETENTION_DAYS` | `7` | 统计数据保留天数 |
137
+
138
+ ## 管理后台
139
+
140
+ 启动服务后访问 `http://localhost:8080`,使用 admin 账号登录。
141
+
142
+ - **Dashboard** — 概览统计、Topic 监控图表、系统配置、快速开始代码
143
+ - **Topics** — Topic 列表、今日消息量、最近消息时间、7 天趋势图
144
+ - **Users** — 用户管理、Token 生成/复制、启停状态
145
+ - **Permissions** — 权限矩阵,按用户/Topic 控制推送(P)和订阅(S)权限
146
+
147
+ ## 数据库
148
+
149
+ 默认使用 SQLite,可通过配置切换到 MySQL:
150
+
151
+ ```bash
152
+ # SQLite(默认)
153
+ PULSE_DB_TYPE=sqlite
154
+ PULSE_DB_PATH=./pulse_mq.db
155
+
156
+ # MySQL
157
+ PULSE_DB_TYPE=mysql
158
+ PULSE_DB_URL=mysql+pymysql://user:password@host:3306/pulse_mq
159
+ ```
160
+
161
+ ## 开发
162
+
163
+ ```bash
164
+ # 克隆项目
165
+ git clone https://github.com/your-username/pulse-mq.git
166
+ cd pulse-mq
167
+
168
+ # 创建虚拟环境
169
+ uv venv
170
+ source .venv/bin/activate # Linux/macOS
171
+ # 或 .venv\Scripts\activate # Windows
172
+
173
+ # 安装依赖
174
+ uv pip install -e ".[dev]"
175
+
176
+ # 运行测试
177
+ pytest tests/ -v
178
+ ```
179
+
180
+ ## 许可证
181
+
182
+ MIT License
@@ -0,0 +1,159 @@
1
+ # PulseMQ
2
+
3
+ 基于 ZeroMQ 的轻量级消息队列系统,配套 Grafana 风格的 Web 管理后台。
4
+
5
+ ## 特性
6
+
7
+ - **双 Socket 架构** — ROUTER + PULL,消息延迟低
8
+ - **多格式支持** — 字符串、msgpack、PyArrow
9
+ - **Web 管理后台** — Grafana 暗色主题,ECharts 监控图表
10
+ - **权限管理** — 用户 token 认证,按 topic 控制推送/订阅权限
11
+ - **单进程部署** — 一条命令启动全部服务
12
+ - **PyPI 安装** — `pip install pulse-mq` 即可使用
13
+
14
+ ## 快速安装
15
+
16
+ ```bash
17
+ pip install pulse-mq
18
+ ```
19
+
20
+ ## 快速开始
21
+
22
+ ### 启动服务端
23
+
24
+ ```bash
25
+ # 复制配置文件
26
+ cp .env.example .env
27
+
28
+ # 启动服务
29
+ pulse-mq-server
30
+ ```
31
+
32
+ 首次启动会自动生成 admin 密码,输出到控制台和 `admin_credentials.txt`。
33
+
34
+ 管理后台默认地址: `http://localhost:8080`
35
+
36
+ ### 客户端使用
37
+
38
+ ```python
39
+ from pulse_mq import PulseMQClient
40
+
41
+ client = PulseMQClient(
42
+ host="localhost",
43
+ port=5555,
44
+ username="your-username",
45
+ token="your-token",
46
+ )
47
+
48
+ # 订阅消息(回调方式)
49
+ def on_message(msg):
50
+ print(f"Topic: {msg.topic}, Data: {msg.data}, Format: {msg.format}")
51
+
52
+ client.subscribe("metrics/cpu", callback=on_message)
53
+
54
+ # 推送消息
55
+ client.publish("metrics/cpu", data=42.5, format="string")
56
+
57
+ # 运行(阻塞,处理心跳和消息接收)
58
+ client.run()
59
+ ```
60
+
61
+ ### 服务端 SDK
62
+
63
+ ```python
64
+ from pulse_mq import PulseMQServer
65
+
66
+ server = PulseMQServer() # 自动读取 .env 配置
67
+ server.start()
68
+
69
+ # 服务端直接推送消息
70
+ server.publish("alerts/system", data="CPU 过高", format="string")
71
+ server.publish("metrics/batch", data=df, format="pyarrow") # DataFrame
72
+
73
+ server.stop()
74
+ ```
75
+
76
+ ## 消息格式
77
+
78
+ | 格式 | 说明 | 示例 |
79
+ |------|------|------|
80
+ | `string` | 原生字符串,最长 4096 字节 | `client.publish("t/d", data="hello", format="string")` |
81
+ | `msgpack` | MessagePack 序列化 | `client.publish("t/d", data={"key": "val"}, format="msgpack")` |
82
+ | `pyarrow` | PyArrow IPC 格式(支持 DataFrame) | `client.publish("t/d", data=df, format="pyarrow")` |
83
+
84
+ ## Topic 命名规则
85
+
86
+ 格式: `group/topic`(强制一层层级)
87
+
88
+ - 允许字符: `a-z A-Z 0-9 _ - .`
89
+ - 每段长度: 1-128 字符
90
+ - 示例: `metrics/cpu`, `logs/app-error`, `data_1/test.topic`
91
+ - 客户端推送/订阅时,topic 不存在会自动创建
92
+
93
+ ## 配置项
94
+
95
+ 所有配置通过 `.env` 文件或环境变量设置:
96
+
97
+ | 环境变量 | 默认值 | 说明 |
98
+ |---------|--------|------|
99
+ | `PULSE_HOST` | `0.0.0.0` | HTTP 服务监听地址 |
100
+ | `PULSE_HTTP_PORT` | `8080` | HTTP 服务端口 |
101
+ | `PULSE_ZMQ_ROUTER_PORT` | `5555` | ZMQ ROUTER 端口 |
102
+ | `PULSE_ZMQ_PULL_PORT` | `5556` | ZMQ PULL 端口 |
103
+ | `PULSE_DB_TYPE` | `sqlite` | 数据库类型 (sqlite/mysql) |
104
+ | `PULSE_DB_PATH` | `./pulse_mq.db` | SQLite 文件路径 |
105
+ | `PULSE_DB_URL` | - | MySQL 连接 URL |
106
+ | `PULSE_QUEUE_SIZE` | `1000` | 每 topic 内存队列大小 |
107
+ | `PULSE_AUTH_CACHE_TTL` | `60` | auth 缓存刷新间隔(秒) |
108
+ | `PULSE_JWT_SECRET` | 自动生成 | JWT 签名密钥 |
109
+ | `PULSE_JWT_EXPIRE_HOURS` | `24` | JWT 过期时间(小时) |
110
+ | `PULSE_ADMIN_PASSWORD` | 自动生成 | admin 初始密码 |
111
+ | `PULSE_HEARTBEAT_INTERVAL` | `30` | 客户端心跳间隔(秒) |
112
+ | `PULSE_HEARTBEAT_TIMEOUT` | `60` | 服务端断线超时(秒) |
113
+ | `PULSE_STATS_RETENTION_DAYS` | `7` | 统计数据保留天数 |
114
+
115
+ ## 管理后台
116
+
117
+ 启动服务后访问 `http://localhost:8080`,使用 admin 账号登录。
118
+
119
+ - **Dashboard** — 概览统计、Topic 监控图表、系统配置、快速开始代码
120
+ - **Topics** — Topic 列表、今日消息量、最近消息时间、7 天趋势图
121
+ - **Users** — 用户管理、Token 生成/复制、启停状态
122
+ - **Permissions** — 权限矩阵,按用户/Topic 控制推送(P)和订阅(S)权限
123
+
124
+ ## 数据库
125
+
126
+ 默认使用 SQLite,可通过配置切换到 MySQL:
127
+
128
+ ```bash
129
+ # SQLite(默认)
130
+ PULSE_DB_TYPE=sqlite
131
+ PULSE_DB_PATH=./pulse_mq.db
132
+
133
+ # MySQL
134
+ PULSE_DB_TYPE=mysql
135
+ PULSE_DB_URL=mysql+pymysql://user:password@host:3306/pulse_mq
136
+ ```
137
+
138
+ ## 开发
139
+
140
+ ```bash
141
+ # 克隆项目
142
+ git clone https://github.com/your-username/pulse-mq.git
143
+ cd pulse-mq
144
+
145
+ # 创建虚拟环境
146
+ uv venv
147
+ source .venv/bin/activate # Linux/macOS
148
+ # 或 .venv\Scripts\activate # Windows
149
+
150
+ # 安装依赖
151
+ uv pip install -e ".[dev]"
152
+
153
+ # 运行测试
154
+ pytest tests/ -v
155
+ ```
156
+
157
+ ## 许可证
158
+
159
+ MIT License
@@ -0,0 +1,37 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from logging.config import fileConfig
4
+
5
+ from sqlalchemy import engine_from_config, pool
6
+ from alembic import context
7
+
8
+ # Add src to path so pulse_mq can be imported
9
+ sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
10
+
11
+ from pulse_mq.models import Base
12
+
13
+ config = context.config
14
+ if config.config_file_name is not None:
15
+ fileConfig(config.config_file_name)
16
+
17
+ target_metadata = Base.metadata
18
+
19
+
20
+ def run_migrations_offline() -> None:
21
+ context.configure(url=config.get_main_option("sqlalchemy.url"), target_metadata=target_metadata, literal_binds=True)
22
+ with context.begin_transaction():
23
+ context.run_migrations()
24
+
25
+
26
+ def run_migrations_online() -> None:
27
+ connectable = engine_from_config(config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool)
28
+ with connectable.connect() as connection:
29
+ context.configure(connection=connection, target_metadata=target_metadata)
30
+ with context.begin_transaction():
31
+ context.run_migrations()
32
+
33
+
34
+ if context.is_offline_mode():
35
+ run_migrations_offline()
36
+ else:
37
+ run_migrations_online()
File without changes
@@ -0,0 +1,36 @@
1
+ [alembic]
2
+ script_location = alembic
3
+ sqlalchemy.url = sqlite:///./pulse_mq.db
4
+
5
+ [loggers]
6
+ keys = root,sqlalchemy,alembic
7
+
8
+ [handlers]
9
+ keys = console
10
+
11
+ [formatters]
12
+ keys = generic
13
+
14
+ [logger_root]
15
+ level = WARN
16
+ handlers = console
17
+
18
+ [logger_sqlalchemy]
19
+ level = WARN
20
+ handlers =
21
+ qualname = sqlalchemy.engine
22
+
23
+ [logger_alembic]
24
+ level = INFO
25
+ handlers =
26
+ qualname = alembic
27
+
28
+ [handler_console]
29
+ class = StreamHandler
30
+ args = (sys.stderr,)
31
+ level = NOTSET
32
+ formatter = generic
33
+
34
+ [formatter_generic]
35
+ format = %(levelname)-5.5s [%(name)s] %(message)s
36
+ datefmt = %H:%M:%S