modify: 多worker模式插件扩缩容问题

This commit is contained in:
2026-08-27 15:46:44 +08:00
parent eb2529e6c3
commit 53f2ac236e
7 changed files with 552 additions and 24 deletions
-1
View File
@@ -105,7 +105,6 @@ base = [
"fastapi.middleware.cors", "fastapi.middleware.cors",
"fastapi.middleware.gzip", "fastapi.middleware.gzip",
"uvicorn", "uvicorn",
"apscheduler",
"diskcache", "diskcache",
"chongming_cache", "chongming_cache",
"chongming_cache.backends", "chongming_cache.backends",
+10 -2
View File
@@ -29,8 +29,8 @@ dependencies = [
"uvloop", "uvloop",
"chongming-mqtt", "chongming-mqtt",
"httptools", "httptools",
"chongming-cli>=0.1.0", "chongming-cli",
"bytedance>=0.1.0", "bytedance",
] ]
description = "基于 modules-bank 的 fastapi 打包脚手架" description = "基于 modules-bank 的 fastapi 打包脚手架"
name = "chongming-zenoh" name = "chongming-zenoh"
@@ -56,6 +56,14 @@ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/"
[[tool.uv.index]] [[tool.uv.index]]
url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
[tool.uv.workspace]
members = [
"utils/cli",
]
[tool.uv.sources]
chongming-cli = { workspace = true }
[project.scripts] [project.scripts]
gunicorn = "chongming:gunicorn_serve" gunicorn = "chongming:gunicorn_serve"
serve = "chongming:serve" serve = "chongming:serve"
+18 -5
View File
@@ -52,10 +52,18 @@ async def lifespan(app: FastAPI):
# 4.1 全局副本数协调:把当前网关登记进在线列表,供按在线网关分摊 quota # 4.1 全局副本数协调:把当前网关登记进在线列表,供按在线网关分摊 quota
manager.register_gateway() manager.register_gateway()
# 4.2 宿主级 inline worker 领导选举(基于共享 cache 的分布式锁,key 按网关隔离):
# gunicorn 多 worker 下每实例仅一个进程托管 inline worker(避免 N×count 冗余),
# 其余进程只做 HTTP 并经 Zenoh liveliness 发现调用;多实例各选各的 leader。
is_inline_owner = manager.acquire_inline_owner()
app.state.inline_owner_locked = is_inline_owner
# 5. 插件加载(混合模式:inline 进程内加载;external 由独立进程运行, # 5. 插件加载(混合模式:inline 进程内加载;external 由独立进程运行,
# 其路由经 Zenoh 动态注册——进程编排交给 Docker/K8s,网关不拉起进程) # 其路由经 Zenoh 动态注册——进程编排交给 Docker/K8s,网关不拉起进程)
worker_tasks, custom_routers, inline_types, external_types = ( worker_tasks, custom_routers, inline_types, external_types = (
await lifespan_setup.load_workers(app, session, manager) await lifespan_setup.load_workers(
app, session, manager, is_inline_owner=is_inline_owner
)
) )
# 5.1 动态路由注册表常驻启动:订阅 chongming/routes/**,external 插件的 # 5.1 动态路由注册表常驻启动:订阅 chongming/routes/**,external 插件的
@@ -102,6 +110,10 @@ async def lifespan(app: FastAPI):
# 2. 清理 inline workers(cleanup_workers 内部按 load_mode 过滤) # 2. 清理 inline workers(cleanup_workers 内部按 load_mode 过滤)
await lifespan_setup.cleanup_workers(app) await lifespan_setup.cleanup_workers(app)
# 2.1 释放 inline worker 所有权分布式锁(仅持锁进程,避免误删其他进程获得的新锁)
if getattr(app.state, "inline_owner_locked", False):
await manager.release_inline_owner()
# 3. 取消刷新任务 # 3. 取消刷新任务
refresh_task = getattr(app.state, "refresh_task", None) refresh_task = getattr(app.state, "refresh_task", None)
if refresh_task: if refresh_task:
@@ -111,11 +123,12 @@ async def lifespan(app: FastAPI):
except asyncio.CancelledError: except asyncio.CancelledError:
pass pass
# 4. 取消所有 worker 任务 # 4. 取消所有 worker 任务(含 self-heal 接管后拉起的任务)
for task in worker_tasks: all_worker_tasks = list(worker_tasks) + manager.extra_worker_tasks()
for task in all_worker_tasks:
task.cancel() task.cancel()
if worker_tasks: if all_worker_tasks:
await asyncio.gather(*worker_tasks, return_exceptions=True) await asyncio.gather(*all_worker_tasks, return_exceptions=True)
# 4.5 取消后台统计/审计任务 # 4.5 取消后台统计/审计任务
await call_stats.shutdown() await call_stats.shutdown()
+44 -7
View File
@@ -390,7 +390,8 @@ def _discover_plugins() -> list[dict]:
return plugins return plugins
async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerManager): async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerManager,
is_inline_owner: bool = True):
""" """
加载 inline 插件,启动对应的 worker 任务(混合模式)。 加载 inline 插件,启动对应的 worker 任务(混合模式)。
@@ -401,6 +402,11 @@ async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerMana
同时自动发现插件模块中暴露的 APIRouter(约定:模块顶层名为 `router` 的 同时自动发现插件模块中暴露的 APIRouter(约定:模块顶层名为 `router` 的
APIRouter 实例),收集后供 register_custom_routes() 统一注册。 APIRouter 实例),收集后供 register_custom_routes() 统一注册。
is_inline_owner: 本进程是否为宿主级 inline worker 持有者。gunicorn 多 worker
下仅持有者进程真正拉起 inline worker(避免 N×count 冗余副本);非持有者进程
仍 import 插件模块以便注册自定义路由/契约校验,但不 create_workers,worker 经
Zenoh liveliness 被跨进程发现。单进程(uvicorn serve)时为 True,行为不变。
返回: (worker_tasks, custom_routers, inline_types, external_types) 返回: (worker_tasks, custom_routers, inline_types, external_types)
""" """
workers_conf = _discover_plugins() workers_conf = _discover_plugins()
@@ -410,7 +416,8 @@ async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerMana
external_types = sorted({wc["type"] for wc in external_plugins}) external_types = sorted({wc["type"] for wc in external_plugins})
logger.info( logger.info(
f"发现 {len(workers_conf)} 个 worker 插件: " f"发现 {len(workers_conf)} 个 worker 插件: "
f"inline={inline_types or '无'}, external={external_types or '无'}" f"inline={inline_types or '无'}, external={external_types or '无'} "
f"(本进程 inline 持有者: {is_inline_owner})"
) )
for wc in external_plugins: for wc in external_plugins:
@@ -456,11 +463,33 @@ async def load_workers(app: FastAPI, session: zenoh.Session, manager: WorkerMana
custom_routers.append(plugin_router) custom_routers.append(plugin_router)
logger.info(f"发现插件自定义路由: {module_name}.router") logger.info(f"发现插件自定义路由: {module_name}.router")
# 非持有者进程:不立即拉起 inline worker,但仍在下方(build starter 后)
# 注册自愈启动器;worker 经 liveliness 被本进程发现调用,@rpc_method 与
# 自定义路由/契约校验依赖的模块 import 已在上文完成不依赖拉起。
factory = getattr(module, factory_name) factory = getattr(module, factory_name)
if inspect.iscoroutinefunction(factory):
tasks = await factory(session, worker_config, count, worker_manager=manager) # 定义"拉起本类型 worker"的协程,供持有者崩溃后的自愈接管复用;
else: # 用默认参数捕获循环变量,避免闭包迟到绑定。
tasks = factory(session, worker_config, count, worker_manager=manager) def _make_starter(session=session, config=worker_config, cnt=count,
mgr=manager, fac=factory):
async def starter():
if inspect.iscoroutinefunction(fac):
return await fac(session, config, cnt, worker_manager=mgr)
return fac(session, config, cnt, worker_manager=mgr)
return starter
starter = _make_starter()
manager.register_worker_starter(worker_type, starter)
# 非持有者进程:不拉起 inline worker(启动器已注册,持有者崩溃时由 refresh 自愈接管)
if not is_inline_owner:
logger.info(
"本进程非 %s 的 inline 持有者,跳过创建副本(liveliness 发现即可调用)",
worker_type,
)
continue
tasks = await starter()
worker_tasks.extend(tasks) worker_tasks.extend(tasks)
logger.info(f"已加载 Worker 插件: {module_name}.{factory_name} (副本数: {count})") logger.info(f"已加载 Worker 插件: {module_name}.{factory_name} (副本数: {count})")
except Exception as e: except Exception as e:
@@ -660,7 +689,15 @@ async def shutdown_mqtt(app: FastAPI):
async def cleanup_workers(app: FastAPI): async def cleanup_workers(app: FastAPI):
"""调用每个 inline worker 配置中定义的 cleanup 函数。""" """调用每个 inline worker 配置中定义的 cleanup 函数。
仅 inline worker 持有者进程执行:非持有者进程未初始化插件(未调用 create_workers,
on_init 未执行),cleanup/on_cleanup 无资源可清,且可能引用未创建的对象而误报错。
"""
# app.state.inline_owner_locked 恒为 bool(lifespan 4.2 设置),缺省按持有处理
if not getattr(app.state, "inline_owner_locked", True):
logger.info("本进程非 inline worker 持有者,跳过插件 cleanup")
return
workers_conf = _discover_plugins() workers_conf = _discover_plugins()
for wc in workers_conf: for wc in workers_conf:
+102 -2
View File
@@ -6,9 +6,9 @@ import random
import socket import socket
import time import time
import orjson import orjson
from typing import Any, AsyncGenerator, Dict, List, Optional from typing import Any, AsyncGenerator, Awaitable, Callable, Dict, List, Optional
import zenoh import zenoh
from chongming_cache import Cache from chongming_cache import Cache, DistributedLock
from .logger import get_logger from .logger import get_logger
logger = get_logger("worker_manager") logger = get_logger("worker_manager")
@@ -81,6 +81,13 @@ class WorkerManager:
self._gateway_id: str = ( self._gateway_id: str = (
os.environ.get("CHONGMING_GATEWAY_ID") or socket.gethostname() os.environ.get("CHONGMING_GATEWAY_ID") or socket.gethostname()
) )
# inline worker 领导选举持有的分布式锁及持有标记(acquire_inline_owner 时设置)
self._inline_owner_lock: Optional[DistributedLock] = None
self._inline_owner: bool = False
# inline worker 自愈接管:类型 -> 启动协程(load_workers 注册);_started_extra
# 记录接管后拉起的任务,供关停时取消。
self._worker_starters: Dict[str, Callable[[], Awaitable[List[Any]]]] = {}
self._started_extra: List[Any] = []
# ── 全局副本数协调(chongming-cache 单一事实来源)──────────── # ── 全局副本数协调(chongming-cache 单一事实来源)────────────
@@ -154,6 +161,94 @@ class WorkerManager:
gws = [[gw_id, ts] for gw_id, ts in gws if gw_id != self._gateway_id] gws = [[gw_id, ts] for gw_id, ts in gws if gw_id != self._gateway_id]
self._write_gateways(gws) self._write_gateways(gws)
# ── inline worker 领导选举(gunicorn 多 worker 兼容)────────────────
# gunicorn `-w N` 会 fork N 个网关进程,各自跑 lifespan 并拉起 inline worker,
# 副本数变成 N×count(副本数量超预期)。用基于共享 cache 的分布式锁做领导选举:
# 每个网关(gateway_id 隔离的实例/宿主)只有一个进程托管其 inline worker,其余
# 进程只做 HTTP、经 Zenoh liveliness 照常发现调用。
# 与 MQTT Bridge 同一把锁模式:经共享 cache 后端(redis/共享卷 diskcache)可跨
# 机器收敛;key 含 gateway_id,多实例各选各的 leader、互不抢占,不破坏分摊语义。
def acquire_inline_owner(self) -> bool:
"""尝试成为本网关 inline worker 持有者(cache 支撑的分布式锁)。
成功(全局唯一持有者)返回 True,并在后台续约租约;持锁进程崩溃后租约过期,
gunicorn 复活的进程可重新获得(failover)。未配置共享 cache 时单进程视为持有者。
"""
if self.cache is None:
self._inline_owner = True # 无共享 cache:单进程即持有者
return True
lock = DistributedLock(self.cache, f"inline_owner:{self._gateway_id}")
if not lock.acquire():
self._inline_owner = False
return False
lock.start()
self._inline_owner_lock = lock
self._inline_owner = True
logger.info(
"[LOCK] inline owner: 本进程获得持有权 (gw=%s)", self._gateway_id
)
return True
async def release_inline_owner(self) -> None:
"""释放 inline worker 持有权分布式锁(受保护:仅仍由本进程持有时才删除)。"""
lock = self._inline_owner_lock
if lock is not None:
await lock.stop()
lock.release()
self._inline_owner_lock = None
self._inline_owner = False
def is_inline_owner(self) -> bool:
"""返回本进程是否持有 inline worker 所有权(用于管理/诊断)。"""
return bool(self._inline_owner)
def register_worker_starter(self, worker_type: str, starter) -> None:
"""为某 inline 类型注册一个"拉起 worker"的协程(load_workers 调用),
供持有者崩溃后的自愈接管使用。starter: async () -> List[Task]
"""
self._worker_starters[worker_type] = starter
def extra_worker_tasks(self) -> List[Any]:
"""返回因自愈接管而拉起的 worker 任务(供网关关停时取消)。"""
return list(self._started_extra)
async def _maybe_heal_inline(self) -> None:
"""持有者进程崩溃后的自愈接管。
若本进程非持有者,却存在「已配置 inline 类型但没有无线活副本」的情况,
说明原持有者已失效:尝试经分布式锁抢得领导权成为新持有者,并拉起这些 worker。
分布式锁保证同一时刻只有一个进程胜出,因此不会出现双写/分裂(split-brain)。
背景:gunicorn 仅复活死掉的 worker 槽位不足以 failover——槽位重启可能在旧锁
租约尚未过期时发生,新进程抢锁失败而照样跳过 create_workers。本函数随 refresh
每周期(~15s)重试,租约过期后即可接管,形成兜底。
"""
# 已是持有者:正常托管,不走接管路径(避免与常规池任务重复 init)
if self._inline_owner or not self._worker_starters:
return
missing = [
wtype
for wtype in self._worker_starters
if not self._local_workers.get(wtype)
and not self._workers_cache.get(wtype)
]
if not missing:
return
# 仍需保证同一时刻仅一个进程成为新持有者
if not self.acquire_inline_owner():
return
logger.warning(
"[heal] 检出持有者进程失效,本进程接管并拉起 inline workers: %s", missing
)
for wtype in missing:
try:
tasks = await self._worker_starters[wtype]()
self._started_extra.extend(tasks or [])
logger.info("[heal] 已拉起 %s (tasks=%d)", wtype, len(tasks or []))
except Exception as e: # noqa: BLE001
logger.error("[heal] 拉起 %s 失败: %s", wtype, e)
def resolved_count(self, worker_type: str, default: int) -> int: def resolved_count(self, worker_type: str, default: int) -> int:
"""获取该类 worker 的全局目标总量。 """获取该类 worker 的全局目标总量。
@@ -249,6 +344,11 @@ class WorkerManager:
self.heartbeat_gateway() self.heartbeat_gateway()
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001
logger.error(f"网关心跳失败: {e}") logger.error(f"网关心跳失败: {e}")
# 4. inline 持有者崩溃自愈接管(见 _maybe_heal_inline)
try:
await self._maybe_heal_inline()
except Exception as e: # noqa: BLE001
logger.error(f"inline 自愈检查失败: {e}")
except Exception as e: except Exception as e:
logger.error(f"刷新 worker 列表失败: {e}") logger.error(f"刷新 worker 列表失败: {e}")
await asyncio.sleep(self._refresh_interval) await asyncio.sleep(self._refresh_interval)
+162
View File
@@ -0,0 +1,162 @@
"""
inline worker 领导选举 + 自愈接管测试(gunicorn 多 worker 兼容)
基于共享 chongming-cache(DistributedLock)验证 WorkerManager 的领导选举语义:
1. 多"进程"各自 acquire_inline_owner:同一时刻只有一个持有者(无分裂)
2. 不同 gateway_id(多实例)key 隔离,各选各的 leader,互不抢占
3. 持有者在场且副本存活时不触发接管(不重复 init)
4. 持有者崩溃(副本消失)后,非持有者经 _maybe_heal_inline 抢锁接管并调用注册的
starter 拉起 worker
5. 接管后其它进程不再重复接管;再次崩溃可继续 failover
6. 关停释放锁;保护性 release 不误删他人锁
运行: uv run python test/test_inline_leader.py
"""
import asyncio
import os
import sys
import tempfile
from pathlib import Path
_HERE = Path(__file__).resolve().parent # chongming-zenoh/
_PROJ = _HERE
for _p in (_PROJ / "src", _PROJ / "utils" / "worker" / "src"):
sys.path.insert(0, str(_p))
from chongming_cache import Cache
from chongming.app.core.worker_manager import WorkerManager
failures = []
def check(name, cond, detail=""):
status = "PASS" if cond else "FAIL"
print(f"[{status}] {name}" + (f" — {detail}" if detail else ""))
if not cond:
failures.append(name)
class DummySession:
"""占位 session:测试不涉及真实 Zenoh,_maybe_heal_inline 不触碰 session"""
async def try_heal(loser):
"""非持有者进程尝试接管"""
await loser._maybe_heal_inline()
async def main():
tmp = tempfile.TemporaryDirectory()
c1 = Cache(backend="diskcache", cache_store_path=tmp.name)
c2 = Cache(backend="diskcache", cache_store_path=tmp.name + "_g2")
# ── 1. 领导选举:唯一持有者 ──────────────────────────────
m1 = WorkerManager(session=DummySession(), cache=c1)
m2 = WorkerManager(session=DummySession(), cache=c1)
a1 = m1.acquire_inline_owner()
a2 = m2.acquire_inline_owner()
check("1.1 同一时刻仅一进程获得持有权", a1 != a2, f"m1={a1}, m2={a2}")
owner, loser = (m1, m2) if a1 else (m2, m1)
check("1.2 持有者 is_inline_owner()=True", owner.is_inline_owner())
check("1.3 失权者 is_inline_owner()=False", not loser.is_inline_owner())
# ── 2. 释放后可再次竞选 ──────────────────────────────────
await owner.release_inline_owner()
check("2.1 持锁者释放后不再持有", not owner.is_inline_owner())
ra = m1.acquire_inline_owner()
rb = m2.acquire_inline_owner()
check("2.2 释放后可再次成功选举(唯一持有者)", ra != rb, f"m1={ra}, m2={rb}")
await m1.release_inline_owner()
await m2.release_inline_owner()
# ── 3. 不同 gateway_id 隔离(多实例各选各 leader) ────────
# gateway_id 默认取 hostname;同宿主多实例须显式设置 CHONGMING_GATEWAY_ID 区分,
# 这儿模拟两个不同实例(gwA / gwB),验证锁 key 隔离、互不抢占。
_orig_gw = os.environ.get("CHONGMING_GATEWAY_ID")
os.environ["CHONGMING_GATEWAY_ID"] = "gwA"
ga1 = WorkerManager(session=DummySession(), cache=c2)
ga2 = WorkerManager(session=DummySession(), cache=c2)
os.environ["CHONGMING_GATEWAY_ID"] = "gwB"
gb1 = WorkerManager(session=DummySession(), cache=c2)
if _orig_gw is None:
os.environ.pop("CHONGMING_GATEWAY_ID", None)
else:
os.environ["CHONGMING_GATEWAY_ID"] = _orig_gw
acc_a = ga1.acquire_inline_owner() or ga2.acquire_inline_owner()
acc_b = gb1.acquire_inline_owner()
check("3.1 不同 gateway 互不抢占(各自选到 leader)", acc_a and acc_b)
check("3.2 gateway 内部仍只有一个持有者",
ga1.is_inline_owner() != ga2.is_inline_owner())
await ga1.release_inline_owner()
await ga2.release_inline_owner()
await gb1.release_inline_owner()
# ── 4. 自愈接管 ──────────────────────────────────────────
ha = WorkerManager(session=DummySession(), cache=c1)
hb = WorkerManager(session=DummySession(), cache=c1)
got = hb.acquire_inline_owner()
check("4.1 初始 hb 持锁、ha 未持锁", got and not ha.is_inline_owner())
started = []
async def fake_starter():
started.append("doubao")
return []
# 与真实 load_workers 一致:所有进程都注册 starter
ha.register_worker_starter("doubao", fake_starter)
hb.register_worker_starter("doubao", fake_starter)
# 4.2 持有者在场、副本存活 → 不接管
hb._local_workers["doubao"] = ["worker/doubao/worker-0"]
ha._workers_cache["doubao"] = ["worker/doubao/worker-0"]
await try_heal(ha)
check("4.2 持有者在场且副本存活:不接管、不拉起", not ha.is_inline_owner())
# 4.3 持有者崩溃(模拟:锁租约过期 + 副本消失)→ ha 接管并拉起
await hb.release_inline_owner() # 模拟原持有者崩溃后锁租约过期
ha._workers_cache.clear()
hb._local_workers.clear()
await try_heal(ha)
check("4.3 副本消失后非持有者接管", ha.is_inline_owner())
check("4.4 接管时调用 starter 拉起 worker", "doubao" in started)
# 4.5 接管后其它进程不再重复接管/拉起
n_started = len(started)
# 模拟场景:新副本已能被"所有进程"看到
hb._workers_cache["doubao"] = ["worker/doubao/worker-0"]
await try_heal(hb)
check("4.5 接管后其它进程不再重复拉起",
len(started) == n_started or hb.is_inline_owner())
# ── 5. 再次崩溃可继续 failover ───────────────────────────
await ha.release_inline_owner() # 模拟 ha 崩溃后锁租约过期
ha._workers_cache.clear()
hb._workers_cache.clear()
n_started = len(started)
await try_heal(hb)
check("5.1 再次崩溃后 hb 接管", hb.is_inline_owner())
check("5.2 failover 期间新增一次拉起", len(started) > n_started)
# ── 6. 保护性释放:失权者 release 不误删他人锁 ───────────
# 当前持有者为 hb,由 ha(非持有者) 释放——锁 token 不匹配,不应删除
await ha.release_inline_owner()
check("6.1 非持锁者 release 不误删(hb 仍持有)", hb.is_inline_owner())
# 之后真正由持锁者安装
await hb.release_inline_owner()
check("6.2 真正持锁者释放后不再持有", not hb.is_inline_owner())
for c in (c1, c2):
c.close()
print()
if failures:
print(f"FAILED {len(failures)}: {failures}")
return 1
print("ALL PASS")
return 0
if __name__ == "__main__":
sys.exit(asyncio.run(main()))
Generated
+216 -7
View File
@@ -6,6 +6,12 @@ resolution-markers = [
"python_full_version < '3.14'", "python_full_version < '3.14'",
] ]
[manifest]
members = [
"chongming-cli",
"chongming-zenoh",
]
[[package]] [[package]]
name = "aiofiles" name = "aiofiles"
version = "25.1.0" version = "25.1.0"
@@ -445,18 +451,30 @@ wheels = [
[[package]] [[package]]
name = "chongming-cli" name = "chongming-cli"
version = "0.1.2" version = "0.1.2"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" } source = { editable = "utils/cli" }
dependencies = [ dependencies = [
{ name = "chongming-config" }, { name = "chongming-config" },
{ name = "module-bank" }, { name = "module-bank" },
{ name = "sqlite-vfs" }, { name = "sqlite-vfs" },
{ name = "tomli" }, { name = "tomli" },
] ]
sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.1.2/chongming_cli-0.1.2.tar.gz", hash = "sha256:e169b81cb8c5dc6198c685b963b862f7cd27a2ac273da789fc23cbe027323265" }
wheels = [ [package.dev-dependencies]
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.1.2/chongming_cli-0.1.2-py3-none-any.whl", hash = "sha256:f72edb901ba1b355e2937f7fb1e9a9da4144c328bba59ea97421b5041b565e67" }, dev = [
{ name = "twine" },
] ]
[package.metadata]
requires-dist = [
{ name = "chongming-config" },
{ name = "module-bank" },
{ name = "sqlite-vfs" },
{ name = "tomli" },
]
[package.metadata.requires-dev]
dev = [{ name = "twine", specifier = ">=7.0.0" }]
[[package]] [[package]]
name = "chongming-config" name = "chongming-config"
version = "0.1.0" version = "0.1.0"
@@ -602,9 +620,9 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "aiofiles" }, { name = "aiofiles" },
{ name = "amqtt" }, { name = "amqtt" },
{ name = "bytedance", specifier = ">=0.1.0" }, { name = "bytedance" },
{ name = "chongming-cache" }, { name = "chongming-cache" },
{ name = "chongming-cli", specifier = ">=0.1.0" }, { name = "chongming-cli", editable = "utils/cli" },
{ name = "chongming-config" }, { name = "chongming-config" },
{ name = "chongming-database" }, { name = "chongming-database" },
{ name = "chongming-jwt" }, { name = "chongming-jwt" },
@@ -631,7 +649,7 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "chongming-cli" }, { name = "chongming-cli", editable = "utils/cli" },
{ name = "nuitka" }, { name = "nuitka" },
{ name = "pyarmor" }, { name = "pyarmor" },
{ name = "pyinstaller" }, { name = "pyinstaller" },
@@ -724,6 +742,15 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19" },
] ]
[[package]]
name = "docutils"
version = "0.23"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" },
]
[[package]] [[package]]
name = "eclipse-zenoh" name = "eclipse-zenoh"
version = "1.10.0" version = "1.10.0"
@@ -932,6 +959,18 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
] ]
[[package]]
name = "id"
version = "1.6.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "urllib3" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" },
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.19" version = "3.19"
@@ -941,6 +980,48 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
] ]
[[package]]
name = "jaraco-classes"
version = "3.4.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "more-itertools" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" },
]
[[package]]
name = "jaraco-context"
version = "6.1.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" },
]
[[package]]
name = "jaraco-functools"
version = "4.6.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "more-itertools" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/1f/c23395957d41ccf27c4e535c3d334c4051e5395b3752057ba4cbaec35c56/jaraco_functools-4.6.0.tar.gz", hash = "sha256:880c577ec9720b3a052d5bc611fb9f2269b3d87902ef42440df443b88e443280", size = 20837, upload-time = "2026-07-14T01:28:02.544Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl", hash = "sha256:99e3dc0060c5cbe8fcd1cdb36258e2a65ca40f1566b2033b12abb1bb44dd3c30", size = 11677, upload-time = "2026-07-14T01:28:01.59Z" },
]
[[package]]
name = "jeepney"
version = "0.9.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" },
]
[[package]] [[package]]
name = "jiter" name = "jiter"
version = "0.16.0" version = "0.16.0"
@@ -1022,6 +1103,23 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/54/b819eb2f8caedb1bccef0bf6188c051f0d898a177b271e1ed3a5cc0a8b66/jwcrypto-1.5.9-py3-none-any.whl", hash = "sha256:30b1c9afc898eefd112c31d836eb2d7a4c6febcac95d4aa727e963cd42911d8e", size = 120772, upload-time = "2026-08-26T12:54:08.637Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/54/b819eb2f8caedb1bccef0bf6188c051f0d898a177b271e1ed3a5cc0a8b66/jwcrypto-1.5.9-py3-none-any.whl", hash = "sha256:30b1c9afc898eefd112c31d836eb2d7a4c6febcac95d4aa727e963cd42911d8e", size = 120772, upload-time = "2026-08-26T12:54:08.637Z" },
] ]
[[package]]
name = "keyring"
version = "25.7.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "jaraco-classes" },
{ name = "jaraco-context" },
{ name = "jaraco-functools" },
{ name = "jeepney", marker = "sys_platform == 'linux'" },
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
{ name = "secretstorage", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
]
[[package]] [[package]]
name = "loguru" name = "loguru"
version = "0.7.3" version = "0.7.3"
@@ -1096,6 +1194,49 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/d3/0a7a8297ec3041e0352f313e020276a04c59bc9200138094df2a6d374f51/module_bank-0.3.2-py3-none-any.whl", hash = "sha256:524faaa86258ebbdc07df493a041566adfbdf57461487a63bf7161e6aa79a817", size = 19051, upload-time = "2026-05-04T16:21:19.827Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/d3/0a7a8297ec3041e0352f313e020276a04c59bc9200138094df2a6d374f51/module_bank-0.3.2-py3-none-any.whl", hash = "sha256:524faaa86258ebbdc07df493a041566adfbdf57461487a63bf7161e6aa79a817", size = 19051, upload-time = "2026-05-04T16:21:19.827Z" },
] ]
[[package]]
name = "more-itertools"
version = "11.1.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
]
[[package]]
name = "nh3"
version = "0.3.7"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/2f/022b27146d52d24b1b353b003359134788ecbcd6fcdf6283adbd57c0fbc8/nh3-0.3.7.tar.gz", hash = "sha256:71860d01c16f4d8c72e334e0674beb2b0899dbd0bf760de18932ef4390303848", size = 25662, upload-time = "2026-08-23T14:26:30.728Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/88/b594f0e86856b37e182fb663283da419eea6424972506e640e890885467f/nh3-0.3.7-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:91a4dab4e94d9fc54b9f67b1adfb23e81fab7ab43f33c3b8c97be9aa38f789ba", size = 1471147, upload-time = "2026-08-23T14:25:55.259Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/60/847a21339f095c4d4c655af31fa2d18b174585bcc210709facacc7ce205c/nh3-0.3.7-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eae64328e46a25785535afcb6885b6f182ecaf5ee8c88f8c075422db8aacc65b", size = 820463, upload-time = "2026-08-23T14:25:56.803Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/7f/1a103e00aaf5e59f2dee4c2709aac609bb2d4bb74fddaf0dcfade11ed87b/nh3-0.3.7-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4968fe8d2db97c6f047659bf46a449fd8ec377f44ebf3e0a1b96c0d3a333ae32", size = 861456, upload-time = "2026-08-23T14:25:58.087Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/4a/e9c436089a0c80b928011ead0efd156aa7639a19b6064ef58dcedcab8369/nh3-0.3.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:be53a4825585f701955cb9baf49f478f56eb81e20294329fe4bc689dd5dd81fa", size = 1023930, upload-time = "2026-08-23T14:25:59.465Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/5c/aa1468e3e281e78d2b3b7d762ccba59f681af355e971dbd255d5903f7b86/nh3-0.3.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:94fd6e59553fbb9ffd8ba71bbd5a54e3126ba01799a097ae30d5341d750bc6ac", size = 1102614, upload-time = "2026-08-23T14:26:00.869Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/9f/57d186d9d3dd38905dc12dddb3484406cdf6aa0b1ce33639a2d277d4ee1c/nh3-0.3.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:18f4278ecd157d43cb35acd5aae9f35cfa79f546b4922bd86536adc0f6312102", size = 1059915, upload-time = "2026-08-23T14:26:02.388Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/53/097a5ad0b34b15d67a472ef849165a54209fa5fbd3e639801c6fe439ba28/nh3-0.3.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:808def0c8c07843e6e50dc84f532457bfa2cfd17417b219a5d9e7c773709331a", size = 1047402, upload-time = "2026-08-23T14:26:03.897Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/a7/c57a2c70534418310889a65ccfac3525e62f0bc0a8613225903403755ce7/nh3-0.3.7-cp314-cp314t-win32.whl", hash = "sha256:874b7d67a067bd29a59223f6270fc30da4edd8e6d87fd219fc93bcbaa662c946", size = 619895, upload-time = "2026-08-23T14:26:05.105Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/b7/efda1d0a611d940bdfde6893bde1ea6b7b7d48c31273aea48e35b822fd58/nh3-0.3.7-cp314-cp314t-win_amd64.whl", hash = "sha256:614dac4a4c36ad084e78447d16fe898dedd762e354a7ab9cda2984e82f67883d", size = 633456, upload-time = "2026-08-23T14:26:06.661Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/18/3ab564595cb88196f50d26e163ed0fd2acc731ab26ac615df91981885887/nh3-0.3.7-cp314-cp314t-win_arm64.whl", hash = "sha256:157ec1eb7a62f3d9a7badb8d82d89aa810e3e24e097eedfa481a25d0c8a99877", size = 611003, upload-time = "2026-08-23T14:26:07.813Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/0d/c257754bf57f829f307aa226bbe136d3a1356b5a0d08324c7b6bd2a8aacd/nh3-0.3.7-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6c3aa50eb26e9228238271db9f983cbc3b006dfbfeca2d4dc34c33ddc6ac5ea5", size = 1493959, upload-time = "2026-08-23T14:26:09.025Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/42/a687e7091928806e514f89fa2666f25ec9bfe0a902fc4402b25e51ce408b/nh3-0.3.7-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f266d3f1b3647449923a8e406524632220dd5d8b647078dfe45b885d33d10479", size = 859615, upload-time = "2026-08-23T14:26:10.606Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/05/b0e6bef633549a23347d5462aa288fcc42381e7918482062ca3cb456242a/nh3-0.3.7-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8fd1ab205258b29254f72db377d99e2c96aa7653ef3b015ccab0420b094b506", size = 839872, upload-time = "2026-08-23T14:26:12.037Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/40/2a0921d45b20828708bcb56887e47dcf8cae13818de5bf9a01308d348712/nh3-0.3.7-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:19f288c938ec6eef1f5d2c6cab47838e71fef8097e1c1233802be5a6230ba086", size = 1091325, upload-time = "2026-08-23T14:26:13.34Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/d1/9d70e0e418a48280ec0ddc6c1b08b4b1136ebcc31a1625e57ff5c665fa51/nh3-0.3.7-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de2b2aab32ea303405debefdcfc58043d3e635fa3f67b9eb140d2b0e0c0d2563", size = 1042482, upload-time = "2026-08-23T14:26:14.667Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/a7/02dd159d4e71f98607d8d4249cddb7561e77be1a8e4dec77d76e1b68fc99/nh3-0.3.7-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b7279d43323a25225df23576af6594a16693f61431170848b8b2ac21ad4f174", size = 946868, upload-time = "2026-08-23T14:26:16.094Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/ed/c5510c615dce55b6fcc364aa1838142f938beed64f5e4927490dfcaf4405/nh3-0.3.7-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70f5ac8626e899a4bab0ef74ca2f5bd602f49c7b739e6e5026b4afc6d63dac42", size = 832161, upload-time = "2026-08-23T14:26:17.272Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/e3/3212c1a5b5745245d7f18885207bbddb34c56075f34dd682bd539aad55cc/nh3-0.3.7-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:5ffdfcb9a686ffb12765376bcfb6b5b55728516d3c0ee317d29982381ded3df8", size = 849791, upload-time = "2026-08-23T14:26:18.498Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/64/9e36594efad6c290de4240d02cb2bd80c339a4ab1c4de66e599ffa6d9d81/nh3-0.3.7-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bc42bb1193c1e28a1e74c2cabaca178e118a7103e8832699fef8a2b3e2496493", size = 875473, upload-time = "2026-08-23T14:26:19.908Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/0c/1a8985fd43fea5530c0ac890b6f0b423770ee72f111b70b7a77f2dec243a/nh3-0.3.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d56e76bd3cadb09b6b0cef364850811663734b348a25f5f587a2819c495367bd", size = 1036463, upload-time = "2026-08-23T14:26:21.536Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/5d/891e533b716cf00df76ad0ba6485dcfd14d59a6430a3cc99057c4c04004e/nh3-0.3.7-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fd4a70efb45d5372174f718878eb7a35c12677626a63b2f103b23b833457dcac", size = 1116029, upload-time = "2026-08-23T14:26:22.907Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/e5/ae8c0782fce74fb6fcf7234bb3d4017f37ce181b4f9d29369eab21c50a04/nh3-0.3.7-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:15f5fbf090f5c88d61c820e1fc1fceecb6520cca9fe85649c06b57ef9dc9ff62", size = 1076589, upload-time = "2026-08-23T14:26:24.302Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/a4/c3423351e8d864ad756e85e15f0c01433361f14d34e4ed156482c0518f2a/nh3-0.3.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6698a822132beedab80f131c08d8d0ac5a178ddeb488d02ca4b67716ecfac7af", size = 1058871, upload-time = "2026-08-23T14:26:25.674Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/6a/478f153f1d7c0baaa3d1e8bb5fdcee3a6235f90fe44ea969a9d4e2b8c47a/nh3-0.3.7-cp38-abi3-win32.whl", hash = "sha256:6e4280115d44c3b278eef712a86748c1a723105cd79feec46952383117ab4e59", size = 630729, upload-time = "2026-08-23T14:26:26.932Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/b9/34433ccb1f0fe6968dabbb7d4bf5721c6221878ef07832748c06655a6a80/nh3-0.3.7-cp38-abi3-win_amd64.whl", hash = "sha256:618e3059caf41ccdf5dcccb3fa9df4cf6e4efe23d1382a8bbfca272a8a4f8bfc", size = 644462, upload-time = "2026-08-23T14:26:28.294Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/70/e140dffff6e808dc6343598df76e7e2407fd0f581de3524c75fba2e0cf24/nh3-0.3.7-cp38-abi3-win_arm64.whl", hash = "sha256:f04b7d333b27f13ca439da3cf1c75c2fba34f104969f6ce4ac8e7079699c2f4a", size = 621867, upload-time = "2026-08-23T14:26:29.547Z" },
]
[[package]] [[package]]
name = "nuitka" name = "nuitka"
version = "4.1.3" version = "4.1.3"
@@ -1569,6 +1710,20 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
] ]
[[package]]
name = "readme-renderer"
version = "45.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "docutils" },
{ name = "nh3" },
{ name = "pygments" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/51/d3a6ea424652c60f05600d8c2e01a55c913755e7cdad64afabbd1aa16f44/readme_renderer-45.0.tar.gz", hash = "sha256:030a8fac74904f8fba11ad1bb6964e3f76e896dc7e5e71f16af190c9056696d1", size = 36172, upload-time = "2026-06-09T21:05:17.37Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/1b/295bf2fa3e740131778065e5ffa2c481f0e7210182d408e9a2c244ff5b0c/readme_renderer-45.0-py3-none-any.whl", hash = "sha256:3385ed220117104a2bceb4a9dac8c5fdf6d1f96890d7ea2a9c7174fd5c84091f", size = 14134, upload-time = "2026-06-09T21:05:15.85Z" },
]
[[package]] [[package]]
name = "redis" name = "redis"
version = "8.1.0" version = "8.1.0"
@@ -1593,6 +1748,27 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
] ]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "requests" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
]
[[package]]
name = "rfc3986"
version = "2.0.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" },
]
[[package]] [[package]]
name = "rich" name = "rich"
version = "15.0.0" version = "15.0.0"
@@ -1631,6 +1807,19 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" },
] ]
[[package]]
name = "secretstorage"
version = "3.5.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "jeepney" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "84.0.0" version = "84.0.0"
@@ -1813,6 +2002,26 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
] ]
[[package]]
name = "twine"
version = "7.0.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" }
dependencies = [
{ name = "id" },
{ name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" },
{ name = "packaging" },
{ name = "readme-renderer" },
{ name = "requests" },
{ name = "requests-toolbelt" },
{ name = "rfc3986" },
{ name = "rich" },
{ name = "urllib3" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/3c/58f808a359700f39a967dffede33efeac809262c03303fa3eec6afff8f49/twine-7.0.0.tar.gz", hash = "sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177", size = 215032, upload-time = "2026-07-27T15:59:00.825Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/08/ddcdc06225eaad6de0e48e1002b06d919dbde20582d0662c7af51308e5d6/twine-7.0.0-py3-none-any.whl", hash = "sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7", size = 43204, upload-time = "2026-07-27T15:58:59.26Z" },
]
[[package]] [[package]]
name = "typer" name = "typer"
version = "0.27.1" version = "0.27.1"