modify: auth插件拆分,将token签发部分交给网关

This commit is contained in:
2026-09-10 16:43:17 +08:00
parent 93be99141f
commit 7cb5771e06
10 changed files with 467 additions and 37 deletions
+5 -3
View File
@@ -328,7 +328,7 @@ inline worker 的生命周期交给**基于分布式锁的宿主级领导选举*
`Worker 管理`(`/workers`,深色主题):
- **登录**:经 auth 插件 `POST /auth/login` 领取管理 Token(无需手填 JWT),从 JWT 解析当前用户与超管标识。
- **登录**:内置网关端点 `POST /auth/login`(auth 域拆分后登录/签发不依赖 auth 插件,插件缩容或故障时登录与 admin API 仍可用),领取管理 Token(无需手填 JWT),从 JWT 解析当前用户与超管标识。
- **鉴权**:仅 `is_superuser=true` 可管理;权限不足时前端/后端弹出对应提示(403)。
- **列表分页**:展示副本(插件类型 / 副本 ID / 模式 inline|external / 状态),内置分页。
- **查询与筛选**:列表顶部统一筛选栏——关键字搜索(插件类型 / 副本 ID / 实例 ID)、模式筛选(全部 / inline / external)、副本数 ≥ N 过滤;**同时作用于下方「逐实例精细扩缩」分组表**(副本数过滤仅对分组表生效),筛选条件变化自动回到第一页。
@@ -729,7 +729,8 @@ uv pip install -e utils/<name> # 安装/更新工具包
| 插件 | 路径 | 功能 |
| ------------------------ | ----------------------------------- | ------------------------------------------------------- |
| **auth** | `src/plugins/auth/` | 用户注册/登录/资料管理,JWT 鉴权,管理员初始化 |
| **auth** | `src/plugins/auth/` | 用户管理插件:注册/资料/改密(登录与 token 签发已内置网关,Users 模型/种子/校验/签发共享于 `chongming-auth` 包) |
| **auth_oauth** | `src/plugins/auth_oauth/` | 身份源插件示例:GitHub OAuth + 手机验证码登录,基于 `chongming-auth` 契约签发平台 token |
| **doubao** | `src/plugins/doubao/` | 豆包视觉理解,支持 AICC 安全通信通道 |
| **aibox_sophon_adapter** | `src/plugins/aibox_sophon_adapter/` | AI Box 设备心跳监控、离线检测、告警上报与查询、视频上传 |
| **example** | `src/plugins/example/` | 四则运算 + 心跳演示(含 MQTT 端点示例) |
@@ -741,6 +742,7 @@ uv pip install -e utils/<name> # 安装/更新工具包
| 包名 | 路径 | 语言 | 说明 |
| ---------------------------- | ----------------------- | ----------- | ---------------------------------------------- |
| **chongming-auth** | `utils/auth/` | Python | auth 域共享契约:Users 模型、建库/种子、登录校验与 JWT 签发(网关内置登录与 auth 插件共用) |
| **chongming-config** | `utils/config/` | Python | TOML 配置加载,TypedDict 类型安全 |
| **chongming-database** | `utils/database/` | Python | aiosqlite + SQLModel 异步数据库封装 |
| **chongming-jwt** | `utils/jwt/` | Rust + PyO3 | JWT 编解码,支持 HS256/384/512 + RS256/384/512 |
@@ -837,7 +839,7 @@ uv pip install -e utils/<name> # 安装/更新工具包子包
| 插件 | HTTP 前缀 | MQTT 主题前缀 | 说明 |
| ------------------------ | --------- | ------------------------ | ------------------------------------ |
| **auth** | `/auth` | `auth.*` | 注册、登录、JWT 鉴权、个人信息、改密 |
| **auth** | `/auth` | `auth.*` | 注册、个人信息、改密(登录 `POST /auth/login` 内置网关) |
| **doubao** | `/doubao` | `doubao.*` | 火山引擎豆包视觉理解模型 |
| **example** | `/calc` | `example.*` | 四则运算 + 访问计数 + MQTT 心跳示例 |
| **aibox_sophon_adapter** | `/aibox` | `aibox_sophon_adapter.*` | AI Box 设备心跳适配 + 视频上传 |
+1
View File
@@ -27,6 +27,7 @@ dependencies = [
"httptools",
"bytedance",
"chongming-cache",
"chongming-auth",
]
description = "基于 modules-bank 的 fastapi 打包脚手架"
name = "chongming-zenoh"
+12 -1
View File
@@ -22,7 +22,7 @@ from chongming_config import get_config
from .core.logger import get_logger
from .core.static_files import SVFSStaticFiles
from .core import lifespan_setup, call_stats
from .routes import storage_router, stats_router, worker_admin_router
from .routes import storage_router, stats_router, worker_admin_router, auth_router
config = get_config()
@@ -39,6 +39,11 @@ async def lifespan(app: FastAPI):
# 1. 本地存储(替代 Nanio S3,省 ~172 MB)
lifespan_setup.setup_storage(app)
# 1.2 内置 auth 库(users 表 + admin 种子)—— auth 域的基础设施部分:
# 登录/签发内置在网关(管理平面底线),不依赖 auth 插件是否加载;
# 与 auth 插件(用户管理)共用同一张 users 表
await lifespan_setup.setup_auth_store(app)
# 2. Zenoh
session = lifespan_setup.setup_zenoh(app)
@@ -123,6 +128,11 @@ async def lifespan(app: FastAPI):
# 2. 清理 inline workers(cleanup_workers 内部按 load_mode 过滤)
await lifespan_setup.cleanup_workers(app)
# 2.2 关闭内置 auth 库
auth_db = getattr(app.state, "auth_db", None)
if auth_db is not None:
await auth_db.dispose()
# 2.1 释放 inline worker 所有权分布式锁(仅持锁进程,避免误删其他进程获得的新锁)
if getattr(app.state, "inline_owner_locked", False):
await manager.release_inline_owner()
@@ -303,3 +313,4 @@ if vfs_db_path := config[env].get("file_system", {}).get("path", None):
app.include_router(storage_router)
app.include_router(stats_router)
app.include_router(worker_admin_router)
app.include_router(auth_router)
+28 -1
View File
@@ -244,6 +244,33 @@ def setup_storage(app: FastAPI):
app.state.upload_root = upload_root
logger.info(f"本地存储已就绪: {upload_root}")
async def setup_auth_store(app: FastAPI) -> None:
"""初始化内置 auth 库(users 表 + admin 种子)—— auth 域的基础设施部分。
拆分自 plugins.auth:登录/签发内置在网关(管理平面底线),auth 插件
缩容到 0 或全部副本故障时登录与 admin API 仍可用。库位置与 auth 插件
读同一份配置(config/workers/auth/config.toml 的 [workers.config]:
data_dir / database),指向同一张 users 表;插件配置缺失(如彻底移除
auth 插件)时回退默认 ./data/auth.db。
"""
from chongming_auth import open_database, seed_admin
workers_conf = {}
auth_conf_path = "config/workers/auth/config.toml"
if os.path.isfile(auth_conf_path):
try:
with open(auth_conf_path, "rb") as f:
workers_conf = tomllib.load(f).get("workers", {}).get("config", {})
except Exception as e: # noqa: BLE001 - 配置损坏不阻断启动,走默认路径
logger.warning(f"无法读取 auth 插件配置 {auth_conf_path}: {e}(使用默认 data_dir)")
db = await open_database(workers_conf)
created = await seed_admin(db)
app.state.auth_db = db
logger.info(f"内置 auth 库已就绪 (admin 种子: {'新建' if created else '已存在'})")
def setup_zenoh(app: FastAPI) -> zenoh.Session:
"""建立 Zenoh 会话并存入 app.state。"""
zenoh_config = config.get("zenoh", {})
@@ -724,7 +751,7 @@ async def setup_mqtt(app: FastAPI, worker_manager: WorkerManager) -> MQTTBridge
# 排他锁:基于共享缓存(redis / 共享卷 diskcache),保证全局只有一个 Bridge 消费。
# 相比原文件锁,可跨多台主机收敛——多实例共享同一个外部 Broker 时,
# 避免一条 MQTT 请求被 N 台网关上各自消费、各回一份响应。
bridge_lock = DistributedLock(worker_manager.cache, "mqtt_bridge")
bridge_lock = DistributedLock(worker_manager.cache, "mqtt_bridge") # pyright: ignore[reportArgumentType]
if not bridge_lock.acquire():
logger.info(
"MQTT Bridge 已由其他网关实例持有(分布式锁),本网关跳过,"
+2 -1
View File
@@ -1,5 +1,6 @@
from .storage import router as storage_router
from .stats import router as stats_router
from .worker_admin import router as worker_admin_router
from .auth import router as auth_router
__all__ = ["storage_router", "stats_router", "worker_admin_router"]
__all__ = ["storage_router", "stats_router", "worker_admin_router", "auth_router"]
+53
View File
@@ -0,0 +1,53 @@
"""内置登录端点 —— auth 域的基础设施部分(管理平面可用性底线)。
拆分自 plugins.auth:登录 / token 签发不再依赖 auth 插件是否加载,
auth 插件缩容到 0 或全部副本故障时,登录与 admin API 仍可用;
用户管理功能(注册 / 资料 / 改密)仍由 auth 插件提供。
共享契约(Users 模型 / claims / 失败文案)来自 chongming_auth 包,
路径与响应 shape 与拆分前完全一致(前端零改动)。
"""
import time
from chongming_auth import (
DEFAULT_TOKEN_EXPIRES_SECONDS,
LoginPostRequest,
LoginPostResponse,
issue_token,
touch_last_login,
verify_login,
)
from chongming_config import get_config
from fastapi import APIRouter, Request
config = get_config()
router = APIRouter(prefix=config["default"]["prefix"], tags=["auth"])
@router.post(
"/auth/login",
response_model=LoginPostResponse,
summary="登录",
description="根据用户名和密码进行登录,登录成功返回token,获取请求需带式token进行请求",
)
async def login(body: LoginPostRequest, request: Request) -> LoginPostResponse:
db = request.app.state.auth_db
user, message = await verify_login(db, body.username, body.password)
if user is None:
return LoginPostResponse(
result="fail", token="", message=message, timestamp=time.time()
)
await touch_last_login(db, user)
security = get_config()["security"]
token = issue_token(
user,
security["secret_key"],
security["algorithm"],
expires_in_seconds=DEFAULT_TOKEN_EXPIRES_SECONDS,
)
return LoginPostResponse(
result="success", token=token, message="登录成功", timestamp=time.time()
)
+113
View File
@@ -0,0 +1,113 @@
"""
内置登录端到端测试(auth 域拆分验证)
验证拆分目标:POST /api/v1/auth/login 内置在网关,不依赖 auth 插件——
本测试只挂载内置 auth 路由(不加载任何插件),登录/签发/响应 shape 全部可用。
同时校验与拆分前 plugins.auth.login 的兼容性:
- 路径 /api/v1/auth/login、响应字段 {result, token, message, timestamp}
- 失败文案逐字一致(用户名错误 / 密码错误)
- token 可被 chongming_jwt 解密回读,claims 含 is_superuser
(网关 admin API 判超管的契约)
"""
import asyncio
import time
from pathlib import Path
import orjson
import pytest
from chongming_auth import open_database, seed_admin
from chongming_config import get_config
from chongming_jwt import JWT
from fastapi import FastAPI
from fastapi.testclient import TestClient
from chongming.app.routes.auth import router as auth_router
_SECURITY = get_config()["security"]
SECRET, ALGORITHM = _SECURITY["secret_key"], _SECURITY["algorithm"]
PREFIX = get_config()["default"]["prefix"]
@pytest.fixture()
def client(tmp_path: Path):
"""最小 app:仅内置 auth 路由 + 临时 auth 库(模拟 auth 插件完全缺席)。"""
async def _build():
db = await open_database({"data_dir": str(tmp_path)})
await seed_admin(db)
return db
db = asyncio.run(_build())
app = FastAPI()
app.include_router(auth_router)
app.state.auth_db = db
with TestClient(app) as c:
yield c
asyncio.run(db.dispose())
def test_login_success_shape_and_claims(client):
resp = client.post(
f"{PREFIX}/auth/login", json={"username": "admin", "password": "admin"}
)
assert resp.status_code == 200
data = resp.json()
# 响应 shape 与拆分前 gen_models 产物一致
assert set(data.keys()) == {"result", "token", "message", "timestamp"}
assert data["result"] == "success"
assert data["message"] == "登录成功"
assert isinstance(data["timestamp"], float)
assert data["timestamp"] == pytest.approx(time.time(), abs=60)
# token 可解密且 claims 契约完整(is_superuser 是 admin API 的判超管依据)
payload = orjson.loads(JWT(SECRET, ALGORITHM).decode(data["token"]))
assert payload["sub"]
assert payload["username"] == "admin"
assert payload["is_superuser"] is True
def test_login_failure_messages_compat(client):
"""失败文案与拆分前 plugins.auth.login 逐字一致。"""
for body, expected_msg in [
({"username": "nouser", "password": "x"}, "用户名错误"),
({"username": "admin", "password": "wrong"}, "密码错误"),
]:
resp = client.post(f"{PREFIX}/auth/login", json=body)
assert resp.status_code == 200
data = resp.json()
assert data["result"] == "fail"
assert data["token"] == ""
assert data["message"] == expected_msg
assert isinstance(data["timestamp"], float)
def test_login_works_without_auth_plugin(client):
"""拆分核心目标:无 auth 插件时登录照常(fixture 未加载任何插件)。"""
resp = client.post(
f"{PREFIX}/auth/login", json={"username": "admin", "password": "admin"}
)
assert resp.status_code == 200
assert resp.json()["result"] == "success"
def test_login_updates_last_login(client, tmp_path):
"""登录成功后 last_login_at 落库(顺带修复拆分前未 commit 的旧问题)。"""
client.post(f"{PREFIX}/auth/login", json={"username": "admin", "password": "admin"})
row = asyncio.run(_read_admin_row(tmp_path))
assert row.last_login_at is not None
async def _read_admin_row(data_dir: Path):
from chongming_auth import Users
from sqlalchemy import select
db = await open_database({"data_dir": str(data_dir)})
try:
async with db.get_session() as sess:
return (
await sess.execute(select(Users).filter_by(username="admin"))
).scalar_one()
finally:
await db.dispose()
+215
View File
@@ -0,0 +1,215 @@
"""
auth_oauth 身份源插件测试(基于 chongming-auth 共享契约的第三方登录示例)
覆盖:
1. OTP 手机验证码登录全流程(演示模式 dev_code):
发码 → 错码拒绝 → 正确码登录签发平台 token → 同号复登复用同一账号(绑定生效)
2. GitHub OAuth 授权码流程(HTTP 调用打桩):
authorize 生成 URL+state → callback find-or-create 签发 token → state 一次性(重放拒绝)
3. 绑定表与共享 users 表落库验证(chongming_auth.Users 可查)
4. 插件导入 + 三方契约校验 0 错误
"""
import asyncio
import tempfile
from pathlib import Path
import orjson
import pytest
from chongming_auth import Users, open_database
from chongming_cache import Cache
from chongming_config import get_config
from chongming_jwt import JWT
from chongming_worker.contracts import validate_worker_contracts
# 与被测插件同源:issue_token 从 get_config()["security"] 读密钥,测试解码必须一致
_SECURITY = get_config()["security"]
SECRET, ALGORITHM = _SECURITY["secret_key"], _SECURITY["algorithm"]
@pytest.fixture()
def env(tmp_path: Path):
"""初始化插件运行环境:共享 auth 库 + 临时 sqlite 缓存 + global_var 注入。
注意:插件模块 @rpc_method 注册在进程内只执行一次,fixture 不清空注册表
(clear_registry 会导致后续测试 import no-op、注册表为空)。
"""
import plugins.auth_oauth # noqa: F401 触发 @rpc_method 注册(幂等)
import plugins.auth_oauth.global_var as gv
async def _build():
db = await open_database({"data_dir": str(tmp_path)})
cache = Cache(backend="sqlite", path=str(tmp_path / "cache.sqlite"))
return db, cache
db, cache = asyncio.run(_build())
gv.set_global_var("db", db)
gv.set_global_var("cache", cache)
gv.set_global_var("plugin_config", {
"return_otp_in_response": True,
"github": {
"client_id": "test-client-id",
"client_secret": "test-client-secret",
"redirect_uri": "http://localhost:5173/auth/oauth/github/callback",
"scope": "read:user user:email",
},
})
yield gv
async def _teardown():
await db.dispose()
cache.close()
asyncio.run(_teardown())
def _decode(token: str) -> dict:
return orjson.loads(JWT(SECRET, ALGORITHM).decode(token))
# ---------- 手机验证码登录 ----------
def test_otp_full_flow(env):
from plugins.auth_oauth.router.phone import (
PhoneLoginPostRequest,
PhoneSendCodePostRequest,
phone_login,
phone_send_code,
)
phone = "13800138000"
# 1. 发码(演示模式返回 dev_code)
sent = asyncio.run(phone_send_code(PhoneSendCodePostRequest(phone=phone)))
assert sent.result == "success" and sent.dev_code
# 2. 错码拒绝
wrong = asyncio.run(phone_login(
PhoneLoginPostRequest(phone=phone, code="000000" if sent.dev_code != "000000" else "111111")
))
assert wrong.result == "fail" and wrong.token == ""
# 3. 正确码登录 → 平台 token
ok = asyncio.run(phone_login(
PhoneLoginPostRequest(phone=phone, code=sent.dev_code)
))
assert ok.result == "success" and ok.token
claims = _decode(ok.token)
assert claims["username"] == phone
assert claims["is_superuser"] is False
# 4. 验证码一次性
replay = asyncio.run(phone_login(
PhoneLoginPostRequest(phone=phone, code=sent.dev_code)
))
assert replay.result == "fail"
# 5. 复登(再次发码登录)→ 复用同一账号(绑定生效,不重复建号)
sent2 = asyncio.run(phone_send_code(PhoneSendCodePostRequest(phone=phone)))
ok2 = asyncio.run(phone_login(
PhoneLoginPostRequest(phone=phone, code=sent2.dev_code)
))
assert _decode(ok2.token)["sub"] == claims["sub"]
def test_otp_invalid_phone(env):
from plugins.auth_oauth.router.phone import (
PhoneSendCodePostRequest, phone_send_code,
)
resp = asyncio.run(phone_send_code(PhoneSendCodePostRequest(phone="abc")))
assert resp.result == "fail" and "手机号" in resp.message
# ---------- GitHub OAuth ----------
def test_github_authorize_and_callback(env, monkeypatch):
from plugins.auth_oauth.router.github import (
GithubCallbackPostRequest, github_authorize, github_callback,
)
from plugins.auth_oauth.providers import github as github_provider
# authorize:URL 含 client_id 与 state,state 已入缓存
authz = asyncio.run(github_authorize())
assert authz.result == "success"
assert "client_id=test-client-id" in authz.authorize_url
assert f"state={authz.state}" in authz.authorize_url
assert env.cache.get(f"auth_oauth:state:{authz.state}")
# 打桩:授权码换取与用户拉取(免外网)
fake_profile = {
"uid": "97594220", "username": "chongming-demo",
"email": "demo@example.com", "full_name": "Chongming Demo",
"avatar_url": "https://avatars.githubusercontent.com/u/97594220",
}
exchanged = {}
async def fake_exchange(client_id, client_secret, code, redirect_uri):
exchanged["code"] = code
return "fake-access-token"
async def fake_fetch(access_token):
assert access_token == "fake-access-token"
return dict(fake_profile)
monkeypatch.setattr(github_provider, "exchange_code", fake_exchange)
monkeypatch.setattr(github_provider, "fetch_user", fake_fetch)
resp = asyncio.run(github_callback(
GithubCallbackPostRequest(code="gh-code", state=authz.state)
))
assert resp.result == "success" and resp.token
claims = _decode(resp.token)
assert claims["username"] == "chongming-demo"
assert claims["email"] == "demo@example.com"
assert exchanged["code"] == "gh-code"
# state 一次性:重放拒绝
replay = asyncio.run(github_callback(
GithubCallbackPostRequest(code="gh-code", state=authz.state)
))
assert replay.result == "fail" and "state" in replay.message
# 落库验证:users 表有新账号 + 绑定指向同一账号;再次登录(新 state)复用
async def _read_users():
db = env.db
from sqlalchemy import select
async with db.get_session() as sess:
return (await sess.execute(select(Users))).scalars().all()
users = asyncio.run(_read_users())
assert len(users) == 1
assert users[0].email == "demo@example.com"
async def _second_login():
from plugins.auth_oauth.router.github import github_authorize as _a
a = await _a()
return await github_callback(
GithubCallbackPostRequest(code="gh-code-2", state=a.state)
)
second = asyncio.run(_second_login())
assert _decode(second.token)["sub"] == claims["sub"]
def test_github_callback_bad_state(env):
from plugins.auth_oauth.router.github import (
GithubCallbackPostRequest, github_callback,
)
resp = asyncio.run(github_callback(
GithubCallbackPostRequest(code="x", state="not-exist")
))
assert resp.result == "fail" and "state" in resp.message
# ---------- 契约校验 ----------
def test_contract_validation(env):
import plugins.auth_oauth # noqa: F401 确保注册
issues = validate_worker_contracts("auth_oauth")
errors = [i for i in issues if i.get("level") == "error"]
assert errors == [], f"契约校验存在错误: {errors}"
+11 -25
View File
@@ -1,37 +1,25 @@
"""
并发启动竞态测试:模拟 gunicorn 多 worker 同时启动时,
多个进程并发调用 plugins.auth.create_workers 初始化默认管理员。
多个进程并发执行网关内置的 admin 种子(chongming_auth.seed_admin)。
auth 域拆分后,admin 种子从 plugins.auth 的 on_init 迁移到网关
lifespan(setup_auth_store → chongming_auth.seed_admin),本测试随之改指向。
预期:不抛 IntegrityError,users 表中只有一条 admin 记录。
"""
import asyncio
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "src"))
import plugins.auth as auth_plugin
def fake_create_workers(session, config, count=1, worker_manager=None):
"""替换真实 worker 启动,避免依赖 zenoh 会话。与原始实现同为同步函数。"""
return []
from chongming_auth import open_database, seed_admin
async def main():
# 替换 Plugin 基类的 worker 创建实现,避免依赖 zenoh 会话
auth_plugin.plugin._create_workers_impl = fake_create_workers
with tempfile.TemporaryDirectory() as tmp:
config = {"type": "auth", "data_dir": tmp}
db = await open_database({"data_dir": tmp})
# 模拟 4 个 gunicorn worker 进程同时启动
results = await asyncio.gather(
auth_plugin.create_workers(None, config, 3),
auth_plugin.create_workers(None, config, 3),
auth_plugin.create_workers(None, config, 3),
auth_plugin.create_workers(None, config, 3),
*[seed_admin(db) for _ in range(4)],
return_exceptions=True,
)
@@ -42,15 +30,11 @@ async def main():
raise RuntimeError("并发启动存在未捕获异常")
# 校验 users 表中应只有一条 admin
from chongming_database import Database
from plugins.auth.models.auth import Users
from chongming_auth import Users
from sqlalchemy import select
db = Database(Path(tmp) / "auth.db")
await db.initialize()
async with db.get_session() as sess:
res = await sess.execute(select(Users))
rows = res.scalars().all()
rows = (await sess.execute(select(Users))).scalars().all()
print("✅ 4 个并发启动全部成功")
print(f"✅ users 表记录数: {len(rows)}(应为 1)")
@@ -58,6 +42,8 @@ async def main():
print(f" - {u.username} <{u.email}> superuser={u.is_superuser}")
assert len(rows) == 1, f"预期 1 条 admin,实际 {len(rows)} 条"
assert sum(1 for r in results if r is True) == 1, "应恰好一个并发方完成新建"
await db.dispose()
if __name__ == "__main__":
Generated
+27 -6
View File
@@ -429,22 +429,39 @@ wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" },
]
[[package]]
name = "chongming-auth"
version = "0.1.0"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
dependencies = [
{ name = "chongming-database" },
{ name = "chongming-jwt" },
{ name = "chongming-password" },
{ name = "orjson" },
{ name = "pydantic" },
{ name = "sqlmodel" },
]
sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-auth/0.1.0/chongming_auth-0.1.0.tar.gz", hash = "sha256:745aa621e9ad4ed3ca583c2d310362869dce63e9731f5581d9b777b55203487d" }
wheels = [
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-auth/0.1.0/chongming_auth-0.1.0-py3-none-any.whl", hash = "sha256:a89948aa9a0229a79e0f1e2285f1ecf60cefc0c029a7fbdabf0b244a9b902d9a" },
]
[[package]]
name = "chongming-cache"
version = "0.2.1"
version = "0.2.2"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
dependencies = [
{ name = "orjson" },
{ name = "redis" },
]
sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.1/chongming_cache-0.2.1.tar.gz", hash = "sha256:4945a21b6a53c38a263955fddae85ca8036e3ba62e69b117fcab5fd7cb38cd22" }
sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.2/chongming_cache-0.2.2.tar.gz", hash = "sha256:1c224329747d99171956ae8e4606a08847acfcbfc6664662bca2513f0c0ca1d1" }
wheels = [
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.1/chongming_cache-0.2.1-py3-none-any.whl", hash = "sha256:1ca873e4c069ff24b728bb1b90342d66cb6fa6dd994851a1d0cddf2604f0693d" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cache/0.2.2/chongming_cache-0.2.2-py3-none-any.whl", hash = "sha256:902877168da16e34204982643f44d1313bad5a43bca65d348522523d307f0c16" },
]
[[package]]
name = "chongming-cli"
version = "0.3.0"
version = "0.3.1"
source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simple/" }
dependencies = [
{ name = "chongming-config" },
@@ -452,9 +469,9 @@ dependencies = [
{ name = "sqlite-vfs" },
{ name = "tomli" },
]
sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.3.0/chongming_cli-0.3.0.tar.gz", hash = "sha256:bdd61eb28909587c2b0c8f1cb675606512a242533eeab30b111678868a36c7a0" }
sdist = { url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.3.1/chongming_cli-0.3.1.tar.gz", hash = "sha256:b5925b9b22ffe374720a48d2614a3a06ace020cba53a20e2b24d53c0e1d2305e" }
wheels = [
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.3.0/chongming_cli-0.3.0-py3-none-any.whl", hash = "sha256:b8422c1e648828be157002ba816b9b41c496eb680b3b9b1f00129c92e96fb7b5" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-cli/0.3.1/chongming_cli-0.3.1-py3-none-any.whl", hash = "sha256:189c9e43d0c88817844f689e32686c2f4b66f4e25e89ab23c1adb135d7a3f4e2" },
]
[[package]]
@@ -491,6 +508,7 @@ source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simpl
wheels = [
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-linux_aarch64.whl", hash = "sha256:0512956acaa7a17aaf9708da8d7b3a7dc366ef0226f03550d7b1b06940ba733b" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-linux_x86_64.whl", hash = "sha256:527676e710d038f270093d0964c92fc5e976f1f5db23e846e3819b1c8a566721" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:ec47b7698e928141c9828200bed4377754c296e688f29d694f9b1cc3d5f00bf0" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:d2329de197af17141048da32b52dcda87ff578353b4fae78c3bbd0043244cdfc" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-jwt/0.1.0/chongming_jwt-0.1.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:d63cc24550b7084e55d4dd0cb9f99cbd0a9d77da39ad0685698fd484e4376e72" },
]
@@ -514,6 +532,7 @@ source = { registry = "http://124.71.68.6:3000/api/packages/chongming/pypi/simpl
wheels = [
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-linux_aarch64.whl", hash = "sha256:752651cce38a999466e9b342f18f0ceb78b17907646440d2b1c6306ebc9c7648" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-linux_x86_64.whl", hash = "sha256:97414ddb764a8efa0183eb6a4519530a8522729ff633a06285a88b7b22fa77b4" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-manylinux_2_31_aarch64.whl", hash = "sha256:22109f4d1d1263bf840554601da9eb2206fb56ba7deae6828f4ebf710edb8dd1" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:65fbe21f0d9cd152e0142b129e23b1697234f5eeba6a9d0ded1a14949849cca3" },
{ url = "http://124.71.68.6:3000/api/packages/chongming/pypi/files/chongming-password/0.1.0/chongming_password-0.1.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:9620458d04e1cc9c93842f7b960b2b7af8b9bc5f88643e785d12686301071b3c" },
]
@@ -565,6 +584,7 @@ dependencies = [
{ name = "aiofiles" },
{ name = "amqtt" },
{ name = "bytedance" },
{ name = "chongming-auth" },
{ name = "chongming-cache" },
{ name = "chongming-jwt" },
{ name = "chongming-mqtt" },
@@ -601,6 +621,7 @@ requires-dist = [
{ name = "aiofiles" },
{ name = "amqtt" },
{ name = "bytedance" },
{ name = "chongming-auth" },
{ name = "chongming-cache" },
{ name = "chongming-jwt" },
{ name = "chongming-mqtt" },