commit d8ebc6bb7bddc6459daf3e097be64c78d2091bef Author: chakcy <947105045@qq.com> Date: Fri Jul 17 11:27:56 2026 +0800 add first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c2bd04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +build_nuitka/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv +data +logs +resources +cache_data diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..e3e22cb --- /dev/null +++ b/config.toml @@ -0,0 +1,97 @@ +[default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" +app.name = "chongming" +app.version = "chongming" +env = "development" +prefix = "/api/v1" +upload_path = "uploads" + +[database] +database_path = "./data/database.db" +type = "sqlite" +url = "sqlite+aiosqlite:///./data/database.db" + +[database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 +echo = true +echo_pool = true +max_overflow = 10 +pool_pre_ping = true +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 + +[scheduler] +job_store_path = "scheduler_jobs.db" + +[cache] +cache_store_path = "./cache_data" + +[security] +access_token_expire_minutes = 1440 +algorithm = "HS256" +max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "your-secret-key-change-in-production" + +[development] +debug = true +env = "development" + +[development.server] +access_log = false +host = "0.0.0.0" +port = 8000 +reload = true + +[development.logging] +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "DEBUG" + +[development.cors] +allow_credentials = true +allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["*"] + +[plugins_worker] +config_path = "./config/workers/config.toml" + +[pyinstaller_build_import] +base = [ + "utils", # public 骨架 + "utils.launch", + "utils.config", + "zenoh", + "chongming_worker", + "server", + "module_bank", # 模块化打包工具 + "sqlite_vfs", + "sqlite_vfs.core", + "fastapi", # web 应用依赖 + "fastapi.staticfiles", + "fastapi.middleware.cors", + "uvicorn", + "apscheduler", + "diskcache", + "orjson", + "python_multipart", + "nanio", + "boto3", +] +linux = [ + "gunicorn.glogging", + "gunicorn.app", + "gunicorn.app.wsgiapp", +] +windows = [] + +[production.module_system] +path = [ + ["resources/app.mbank", "src/chongming/app", "app"], + ["resources/plugins.mbank", "src/plugins", "plugins"], +] +type = "module-bank" \ No newline at end of file diff --git a/config/workers/config.toml b/config/workers/config.toml new file mode 100644 index 0000000..8203a18 --- /dev/null +++ b/config/workers/config.toml @@ -0,0 +1,6 @@ +[[workers]] +type = "example" +module = "plugins.example" +factory = "create_workers" +count = 3 +config = { type = "example" } \ No newline at end of file diff --git a/config/workers/example/add.toml b/config/workers/example/add.toml new file mode 100644 index 0000000..4409bf5 --- /dev/null +++ b/config/workers/example/add.toml @@ -0,0 +1,16 @@ +# add 接口 — 按请求类型分节 +# subject 会带上方法后缀: example.add.GET / example.add.POST + +[GET] +cacheable = true +summary = "Add two numbers" +docstring = "Add two numbers via GET query params" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "add" }, timestamp = { type = "float", required = false, default = 0.0 } } + +[POST] +cacheable = true +summary = "Add two numbers (POST)" +docstring = "Add two numbers via POST JSON body" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "add" }, timestamp = { type = "float", required = false, default = 0.0 } } diff --git a/config/workers/example/config.toml b/config/workers/example/config.toml new file mode 100644 index 0000000..bf1e388 --- /dev/null +++ b/config/workers/example/config.toml @@ -0,0 +1,13 @@ +[worker] +name = "example" +version = "0.1.0" +description = "chongming worker example — demonstrates new config features" +config_version = "1.0" +defaults = { method = "GET", ttl = 30, timeout = 2.0, auth_required = true, cacheable = false } + + [worker.registration] + type = "register" + service = "example" + worker_type = "calc-workers" + router_prefix = "/calc" + tags = ["example"] diff --git a/config/workers/example/divide.toml b/config/workers/example/divide.toml new file mode 100644 index 0000000..984c4c8 --- /dev/null +++ b/config/workers/example/divide.toml @@ -0,0 +1,7 @@ +# divide 接口 + +[GET] +summary = "Divide two numbers" +docstring = "Divide two numbers via GET query params (a / b). Returns error if b == 0." +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = false, default = 0.0 }, operation = { type = "str", default = "divide" }, timestamp = { type = "float", required = false, default = 0.0 }, error = { type = "str", required = false, default = "" } } diff --git a/config/workers/example/multiply.toml b/config/workers/example/multiply.toml new file mode 100644 index 0000000..80b189e --- /dev/null +++ b/config/workers/example/multiply.toml @@ -0,0 +1,7 @@ +# multiply 接口 + +[GET] +summary = "Multiply two numbers" +docstring = "Multiply two numbers via GET query params (a * b)" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "multiply" }, timestamp = { type = "float", required = false, default = 0.0 } } diff --git a/config/workers/example/subtract.toml b/config/workers/example/subtract.toml new file mode 100644 index 0000000..88dbcd7 --- /dev/null +++ b/config/workers/example/subtract.toml @@ -0,0 +1,7 @@ +# subtract 接口 + +[GET] +summary = "Subtract two numbers" +docstring = "Subtract two numbers via GET query params (a - b)" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "subtract" }, timestamp = { type = "float", required = false, default = 0.0 } } diff --git a/config/zenoh_config.json5 b/config/zenoh_config.json5 new file mode 100644 index 0000000..97602ed --- /dev/null +++ b/config/zenoh_config.json5 @@ -0,0 +1,11 @@ +{ + mode: "peer", + connect: { + endpoints: ["tcp/127.0.0.1:7447"], + }, + scouting: { + multicast: { + enabled: false, + }, + }, +} diff --git a/output.md b/output.md new file mode 100644 index 0000000..9163522 --- /dev/null +++ b/output.md @@ -0,0 +1,5235 @@ +`./pyproject.toml` +``` +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +dependencies = [ + "apscheduler", + "boto3>=1.43.48", + "chongming-worker", + "diskcache", + "eclipse-zenoh", + "fastapi[uvicorn]", + "gunicorn", + "modelvault>=0.16.0", + "module-bank", + "nanio>=0.1.5", + "orjson>=3.11.9", + "python-multipart>=0.0.32", + "sqlite-vfs", + "tomli", + "typing-extensions", + "uvicorn", +] +description = "基于 modules-bank 的 fastapi 打包脚手架" +name = "chongming-zenoh" +readme = "README.md" +requires-python = ">=3.12" +version = "0.1.0" + +[dependency-groups] +dev = [ + "nuitka", + "pyarmor", + "pyinstaller", + "ruff", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming"] + +[[tool.uv.index]] +url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" + +[tool.uv.workspace] +members = [ + "utils/worker", + "utils/request", +] + +[tool.uv.sources] +chongming-worker = { workspace = true } + +[project.scripts] +build = "chongming.scripts.build:build" +gen_models = "chongming.scripts.gen_models:gen_models" +gunicorn = "chongming:gunicorn_serve" +serve = "chongming:serve" + +``` + +`./config.toml` +``` +[default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" +app.name = "chongming" +app.version = "chongming" +env = "development" +prefix = "/api/v1" +upload_path = "uploads" + +[database] +database_path = "./data/database.db" +type = "sqlite" +url = "sqlite+aiosqlite:///./data/database.db" + +[database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 +echo = true +echo_pool = true +max_overflow = 10 +pool_pre_ping = true +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 + +[scheduler] +job_store_path = "scheduler_jobs.db" + +[cache] +cache_store_path = "./cache_data" + +[security] +access_token_expire_minutes = 1440 +algorithm = "HS256" +max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "your-secret-key-change-in-production" + +[development] +debug = true +env = "development" + +[development.server] +access_log = false +host = "0.0.0.0" +port = 8000 +reload = true + +[development.logging] +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "DEBUG" + +[development.cors] +allow_credentials = true +allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["*"] + +[plugins_worker] +config_path = "./config/workers/config.toml" + +[pyinstaller_build_import] +base = [ + "utils", # public 骨架 + "utils.launch", + "utils.config", + "zenoh", + "chongming_worker", + "server", + "module_bank", # 模块化打包工具 + "sqlite_vfs", + "sqlite_vfs.core", + "fastapi", # web 应用依赖 + "fastapi.staticfiles", + "fastapi.middleware.cors", + "uvicorn", + "apscheduler", + "diskcache", + "orjson", + "python_multipart", + "nanio", + "boto3", +] +linux = [ + "gunicorn.glogging", + "gunicorn.app", + "gunicorn.app.wsgiapp", +] +windows = [] + +[production.module_system] +path = [ + ["resources/app.mbank", "src/chongming/app", "app"], + ["resources/plugins.mbank", "src/plugins", "plugins"], +] +type = "module-bank" +``` + +`./README.md` +``` + +``` + +`./build_nuitka/config.toml` +``` +[default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" +app.name = "chongming" +app.version = "0.1.0" +env = "production" +prefix = "/api/v1" +upload_path = "uploads" + +[database] +generate_schemas = true # 生产环境使用迁移工具 +type = "sqlite" +url = "sqlite+aiosqlite:///./data/database.db" + +[database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 +echo = false +echo_pool = false +max_overflow = 10 +pool_pre_ping = true +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 + +[scheduler] +job_store_path = "scheduler_jobs.db" + +[cache] +cache_store_path = "./" + +[security] +access_token_expire_minutes = 30 +algorithm = "HS256" +max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "LClT3DaMKZzvT1L4bgsxvCSM5q7uPeX3R2_FZ8xzwu8=" + +[development] +debug = true +env = "development" + +[development.server] +access_log = false +host = "127.0.0.1" +port = 8001 +reload = true + +[development.logging] +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "DEBUG" + +[development.cors] +allow_credentials = true +allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["*"] + +[production] +debug = false +env = "production" + +[production.server] +access_log = false +host = "0.0.0.0" +port = 8000 +reload = false +timeout_keep_alive = 65 +workers = 4 + +[production.logging] +backup_count = 5 # 最多保存 10 个日志文件 +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "INFO" +max_size = 10485760 # 10MB + +[production.cors] +allow_credentials = true +allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["http://192.168.137.5:8000", "http://localhost:8000", "http://127.0.0.1:8000"] + +[production.module_system] +path = [ + "resources/app.mbank", + "resources/plugins.mbank", +] +type = "module-bank" + +[production.file_system] +path = "static.svfs" +type = "svfs" + +[[workers]] +type = "example" +module = "plugins.example" +factory = "create_workers" +count = 3 +config = { type = "example" } + +``` + +`./build_nuitka/config/workers/example/multiply.toml` +``` +# multiply 接口 + +[GET] +summary = "Multiply two numbers" +docstring = "Multiply two numbers via GET query params (a * b)" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "multiply" }, timestamp = { type = "float", required = false, default = 0.0 } } + +``` + +`./build_nuitka/config/workers/example/divide.toml` +``` +# divide 接口 + +[GET] +summary = "Divide two numbers" +docstring = "Divide two numbers via GET query params (a / b). Returns error if b == 0." +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = false, default = 0.0 }, operation = { type = "str", default = "divide" }, timestamp = { type = "float", required = false, default = 0.0 }, error = { type = "str", required = false, default = "" } } + +``` + +`./build_nuitka/config/workers/example/subtract.toml` +``` +# subtract 接口 + +[GET] +summary = "Subtract two numbers" +docstring = "Subtract two numbers via GET query params (a - b)" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "subtract" }, timestamp = { type = "float", required = false, default = 0.0 } } + +``` + +`./build_nuitka/config/workers/example/add.toml` +``` +# add 接口 — 按请求类型分节 +# subject 会带上方法后缀: example.add.GET / example.add.POST + +[GET] +cacheable = true +summary = "Add two numbers" +docstring = "Add two numbers via GET query params" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "add" }, timestamp = { type = "float", required = false, default = 0.0 } } + +[POST] +cacheable = true +summary = "Add two numbers (POST)" +docstring = "Add two numbers via POST JSON body" +params = ["a: float", "b: float"] +response_model = { result = { type = "float", required = true }, operation = { type = "str", default = "add" }, timestamp = { type = "float", required = false, default = 0.0 } } + +``` + +`./build_nuitka/config/workers/example/config.toml` +``` +[worker] +name = "example" +version = "0.1.0" +description = "chongming worker example — demonstrates new config features" +config_version = "1.0" +defaults = { method = "GET", ttl = 30, timeout = 2.0, auth_required = true, cacheable = false } + + [worker.registration] + type = "register" + service = "example" + worker_type = "calc-workers" + router_prefix = "/calc" + tags = ["example"] + +``` + +`./utils/worker/pyproject.toml` +``` +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +dependencies = [ + "eclipse-zenoh>=1.9.0", + "tomli>=2.4.1", +] +description = "chongming worker" +name = "chongming-worker" +readme = "README.md" +requires-python = ">=3.12" +version = "0.1.0" + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming_worker"] + +``` + +`./utils/worker/README.md` +``` + +``` + +`./utils/worker/src/chongming_worker/decorators.py` +``` +""" +通用 Working 装饰器框架 + +提供 @rpc_method 装饰器,用于将普通函数注册为 Zenoh RPC 端点。 +装饰器自动处理: +- 访问类型(GET/POST/PUT/DELETE) +- 接口名称(subject) +- 参数解析与类型转换 +- 响应序列化 + +兼容 gen_models 命令生成的 Pydantic 模型: +- 通过 request_model / response_model 参数绑定已生成的模型 +- 自动从插件 models 目录发现匹配的模型 +- 请求参数自动校验与响应格式自动序列化 +""" +import asyncio +import json +import time +import inspect +import importlib +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, get_type_hints, Union + +from pydantic import BaseModel, Field, create_model, ValidationError + + +# ============================================================ +# 类型系统 +# ============================================================ + +TYPE_MAP: Dict[str, Any] = { + "float": float, + "int": int, + "str": str, + "bool": bool, + "any": Any, +} + + +def _resolve_type(annotation: Any) -> Any: + """将类型注解解析为可用的 Python 类型""" + if annotation is inspect.Parameter.empty: + return str + origin = getattr(annotation, "__origin__", None) + if origin is list: + args = getattr(annotation, "__args__", (Any,)) + return List[args[0]] if args else List[Any] # type: ignore + if origin is dict: + args = getattr(annotation, "__args__", (str, Any)) + return Dict[args[0], args[1]] if len(args) == 2 else Dict[str, Any] # type: ignore + if origin is tuple: + args = getattr(annotation, "__args__", (Any,)) + return Tuple[tuple(args)] # type: ignore + if origin is Optional: + args = getattr(annotation, "__args__", (Any,)) + return Optional[args[0]] # type: ignore + return annotation + + +def _type_to_config_str(py_type: Any) -> str: + """将 Python 类型转换为配置字符串(用于 gen_models 兼容)""" + origin = getattr(py_type, "__origin__", None) + # 处理 Union 类型(包括 Optional[X]) + if origin is Union: + args = getattr(py_type, "__args__", (Any,)) + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1: + return _type_to_config_str(non_none_args[0]) + return "str" + if origin is list: + args = getattr(py_type, "__args__", (Any,)) + return f"list[{_type_to_config_str(args[0])}]" + if origin is dict: + args = getattr(py_type, "__args__", (str, Any)) + return f"dict[{_type_to_config_str(args[0])}, {_type_to_config_str(args[1])}]" + if py_type is float: + return "float" + if py_type is int: + return "int" + if py_type is bool: + return "bool" + if py_type is str: + return "str" + return "str" + + +# ============================================================ +# 装饰器定义 +# ============================================================ + +class RPCMethod: + """ + 装饰后的 RPC 方法元数据 + + 存储原始函数、访问类型、接口名称、参数信息等, + 供 Worker 在运行时发现和调用。 + + 支持绑定 gen_models 生成的 Pydantic 模型: + - request_model: 用于请求参数的自动校验和转换 + - response_model: 用于响应数据的格式化和序列化 + """ + + def __init__( + self, + func: Callable, + subject: str, + method: str = "GET", + summary: str = "", + timeout: float = 3.0, + request_model: Optional[Type[BaseModel]] = None, + response_model: Optional[Type[BaseModel]] = None, + ): + self.func = func + self.subject = subject + self.method = method.upper() + self.summary = summary or func.__doc__ or "" + self.timeout = timeout + self.name = func.__name__ + + # 绑定的 Pydantic 模型(由 gen_models 生成,或手动传入) + self.request_model: Optional[Type[BaseModel]] = request_model + self.response_model: Optional[Type[BaseModel]] = response_model + + # 解析函数签名 + sig = inspect.signature(func) + hints = get_type_hints(func) + self.params: Dict[str, Any] = {} + self.model_param: Optional[str] = None # 如果有参数是 BaseModel,记录参数名 + self.return_type: Any = str + + for param_name, param in sig.parameters.items(): + if param_name == "self": + continue + py_type = _resolve_type(hints.get(param_name, str)) + # 检查参数类型是否是 BaseModel 子类 + if isinstance(py_type, type) and issubclass(py_type, BaseModel): + self.model_param = param_name + # 如果是 BaseModel 参数,直接用它的字段作为 params(用于配置和文档) + for field_name, field in py_type.model_fields.items(): + self.params[field_name] = _resolve_type(field.annotation) + else: + self.params[param_name] = py_type + + # 返回值类型 + self.return_type = _resolve_type(hints.get("return", Any)) + + def build_pydantic_request_model(self, model_name: str = "AutoRequest") -> Type[BaseModel]: + """根据参数注解生成 Pydantic 请求模型""" + fields: Dict[str, Any] = {} + for param_name, py_type in self.params.items(): + fields[param_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + + def build_pydantic_response_model(self, model_name: str = "AutoResponse") -> Type[BaseModel]: + """根据返回值注解生成 Pydantic 响应模型""" + fields: Dict[str, Any] = {} + # 如果返回类型是 BaseModel 子类,直接使用 + if isinstance(self.return_type, type) and issubclass(self.return_type, BaseModel): + return self.return_type + # 否则包装为 {result: return_type} + fields["result"] = (self.return_type, Field(...)) + return create_model(model_name, **fields) + + def get_request_model(self) -> Optional[Type[BaseModel]]: + """ + 获取有效的请求模型,优先级: + 1. 绑定的 request_model(由用户或 gen_models 提供) + 2. 如果函数参数本身就是 Pydantic 模型(model_param),从中提取字段生成模型 + 3. 自动生成的模型(根据函数参数注解,仅 POST/PUT/PATCH) + """ + if self.request_model is not None: + return self.request_model + # 如果函数接受了 Pydantic 模型参数,用它的字段生成请求模型 + if self.model_param is not None: + sig = inspect.signature(self.func) + hints = get_type_hints(self.func) + model_type = hints.get(self.model_param) + if isinstance(model_type, type) and issubclass(model_type, BaseModel): + return model_type + # 对于 POST/PUT/PATCH,根据 params 自动生成 + if self.params and self.method in ("POST", "PUT", "PATCH"): + return self.build_pydantic_request_model(f"{self.name.title()}_Request") + return None + + def get_response_model(self) -> Optional[Type[BaseModel]]: + """ + 获取有效的响应模型,优先级: + 1. 绑定的 response_model(由用户或 gen_models 提供) + 2. 自动生成的模型(根据函数返回值注解) + """ + if self.response_model is not None: + return self.response_model + return self.build_pydantic_response_model(f"{self.name.title()}_Response") + + def validate_request(self, payload: Dict[str, Any]) -> Dict[str, Any]: + """ + 使用 Pydantic 模型校验请求参数 + + Args: + payload: 原始请求参数字典 + + Returns: + 校验通过后的参数字典(包含类型转换) + """ + req_model = self.get_request_model() + if req_model is None: + return payload + try: + validated = req_model(**payload) + return validated.model_dump() + except ValidationError as e: + raise ValueError(f"请求参数校验失败: {e.errors()}") + + def serialize_response(self, raw_result: Any) -> Any: + """ + 使用 Pydantic 模型序列化响应数据 + + Args: + raw_result: 原始函数返回值 + + Returns: + 序列化后的响应数据(dict 或列表等可 JSON 序列化的类型) + """ + resp_model = self.get_response_model() + if resp_model is None: + return raw_result + if isinstance(raw_result, dict): + try: + validated = resp_model(**raw_result) + return validated.model_dump() + except ValidationError: + # 如果无法用响应模型校验,返回原始结果 + return raw_result + if isinstance(raw_result, resp_model): + return raw_result.model_dump() + return raw_result + + async def call(self, payload: Dict[str, Any]) -> Any: + """ + 调用原始函数,自动从 payload 提取参数 + + 流程: + 1. 校验请求参数(使用绑定的 request_model 或自动生成) + 2. 提取函数需要的参数 + 3. 调用函数 + 4. 序列化响应(使用绑定的 response_model 或自动生成) + + 支持两种函数签名: + - 展开参数: def add(a: float, b: float) -> ... # 传统方式 + - Pydantic 模型: def add(req: AddGetRequest) -> ... # 工程化方式 + + Args: + payload: 请求参数字典 + + Returns: + 序列化后的响应数据 + """ + # 1. 校验请求参数 + validated_payload = self.validate_request(payload) + + # 2. 提取函数需要的参数 + kwargs = {} + + if self.model_param is not None: + # 2a. 函数参数是 Pydantic 模型 — 将整个校验后的 payload 转为模型实例传入 + req_model = self.get_request_model() + if req_model is not None: + kwargs[self.model_param] = req_model(**validated_payload) + else: + # 如果没有 request_model,从函数签名获取模型类型 + sig = inspect.signature(self.func) + hints = get_type_hints(self.func) + model_type = hints.get(self.model_param) + if isinstance(model_type, type) and issubclass(model_type, BaseModel): + kwargs[self.model_param] = model_type(**validated_payload) + else: + kwargs[self.model_param] = validated_payload + else: + # 2b. 传统方式:逐字段提取参数 + for param_name in self.params: + if param_name in validated_payload: + kwargs[param_name] = validated_payload[param_name] + + # 3. 调用函数 + if asyncio.iscoroutinefunction(self.func): + result = await self.func(**kwargs) + else: + result = self.func(**kwargs) + + # 4. 序列化响应 + return self.serialize_response(result) + + +# 全局注册表:worker_type -> {subject -> RPCMethod} +_RPC_REGISTRY: Dict[str, Dict[str, RPCMethod]] = {} + + +def rpc_method( + subject: str, + method: str = "GET", + summary: str = "", + timeout: float = 3.0, + request_model: Optional[Type[BaseModel]] = None, + response_model: Optional[Type[BaseModel]] = None, +): + """ + 装饰器:将函数注册为 RPC 端点 + + 用法: + # 基本用法(自动生成模型) + @rpc_method(subject="calc.add", method="GET", summary="Add two numbers") + def add(a: float, b: float) -> float: + return a + b + + # 绑定 gen_models 生成的 Pydantic 模型 + @rpc_method( + subject="calc.add", + method="GET", + summary="Add two numbers", + response_model=AddGetResponse, + ) + def add(a: float, b: float): + return a + b + + 参数: + subject: 接口名称,格式为 "worker_type.operation" + method: HTTP 方法 (GET/POST/PUT/DELETE) + summary: 接口描述 + timeout: 超时时间(秒) + request_model: 可选的 Pydantic 请求模型(用于验证请求参数) + response_model: 可选的 Pydantic 响应模型(用于格式化响应) + """ + def decorator(func: Callable) -> Callable: + rpc = RPCMethod( + func=func, + subject=subject, + method=method, + summary=summary, + timeout=timeout, + request_model=request_model, + response_model=response_model, + ) + # 提取 worker_type(subject 的第一部分) + worker_type = subject.split(".")[0] if "." in subject else "default" + if worker_type not in _RPC_REGISTRY: + _RPC_REGISTRY[worker_type] = {} + # 使用 subject + "#" + method 作为唯一 key,允许多个 HTTP 方法共享同一 subject + registry_key = f"{subject}#{method.upper()}" + _RPC_REGISTRY[worker_type][registry_key] = rpc + + # 给函数附加元数据,方便外部访问 + func._rpc_meta = rpc # type: ignore + return func + + return decorator + + +def bind_model( + subject: str = "", + method: str = "", + request_model: Optional[Type[BaseModel]] = None, + response_model: Optional[Type[BaseModel]] = None, +): + """ + 装饰器:为已注册的 RPC 端点绑定 Pydantic 模型 + + 用于 gen_models 生成模型后,将模型绑定到用 @rpc_method 定义的端点。 + 无需修改原有的 @rpc_method 调用。 + + 用法: + from .models.pydantic import AddGetResponse + + @bind_model(subject="example.add", method="GET", response_model=AddGetResponse) + def add(): + pass # 函数体不重要,仅用于匹配已注册的端点 + + 参数: + subject: 要绑定的接口名称(与 @rpc_method 中的 subject 一致) + method: 要绑定的 HTTP 方法 + request_model: Pydantic 请求模型 + response_model: Pydantic 响应模型 + """ + def decorator(func: Callable) -> Callable: + nonlocal subject, method + if not subject and hasattr(func, '_rpc_meta'): + subject = func._rpc_meta.subject # type: ignore[attr-defined] + method = func._rpc_meta.method # type: ignore[attr-defined] + + worker_type = subject.split(".")[0] if "." in subject else "default" + method_upper = method.upper() + registry = _RPC_REGISTRY.get(worker_type, {}) + # 用新 key 格式查找: subject#METHOD + rpc = registry.get(f"{subject}#{method_upper}") + + if rpc is None: + raise ValueError( + f"未找到已注册的 RPC 端点: subject='{subject}', method='{method}'. " + f"请确保 @rpc_method 装饰器已先于 @bind_model 执行。" + ) + + # 如果指定了 method,进一步筛选 + if method and rpc.method != method.upper(): + return func + + if request_model is not None: + rpc.request_model = request_model + if response_model is not None: + rpc.response_model = response_model + + func._rpc_meta = rpc # type: ignore[attr-defined] + return func + + return decorator + + +def auto_bind_models(plugin_name: str): + """ + 自动从插件 models 目录发现并绑定 Pydantic 模型 + + 扫描 src/plugins//models/pydantic.py 中定义的所有模型, + 尝试按命名约定匹配已注册的 RPC 端点。 + + 命名约定: + - 请求模型: Request (e.g., AddPostRequest) + - 响应模型: Response (e.g., AddGetResponse) + + 用法: + # 在插件 __init__.py 中调用 + from ...decorators import auto_bind_models + auto_bind_models("example") + """ + try: + models_module = importlib.import_module(f"plugins.{plugin_name}.models.pydantic") + except ImportError: + try: + models_module = importlib.import_module(f"src.plugins.{plugin_name}.models.pydantic") + except ImportError: + print(f"⚠️ auto_bind_models: 未找到 models 模块 (plugins.{plugin_name}.models.pydantic)") + return + + # 获取模块中所有 BaseModel 子类 + model_classes = {} + for name in dir(models_module): + obj = getattr(models_module, name) + if isinstance(obj, type) and issubclass(obj, BaseModel) and obj is not BaseModel: + model_classes[name] = obj + + if not model_classes: + print(f"⚠️ auto_bind_models: 未找到任何 Pydantic 模型") + return + + registry = _RPC_REGISTRY.get(plugin_name, {}) + bound_count = 0 + + for _key, rpc in registry.items(): + op_name = rpc.subject.split(".")[-1] if "." in rpc.subject else rpc.subject + op_camel = op_name.title().replace("-", "").replace("_", "") + method_tag = rpc.method.title() + + # 尝试匹配请求模型: Request + req_model_name = f"{op_camel}{method_tag}Request" + if req_model_name in model_classes: + rpc.request_model = model_classes[req_model_name] + bound_count += 1 + print(f" ✓ 绑定请求模型: {req_model_name} -> {rpc.subject} [{rpc.method}]") + + # 尝试匹配响应模型: Response + resp_model_name = f"{op_camel}{method_tag}Response" + if resp_model_name in model_classes: + rpc.response_model = model_classes[resp_model_name] + bound_count += 1 + print(f" ✓ 绑定响应模型: {resp_model_name} -> {rpc.subject} [{rpc.method}]") + + if bound_count > 0: + print(f"✅ auto_bind_models: 已绑定 {bound_count} 个模型到 {plugin_name} 的 RPC 端点") + else: + print(f"⚠️ auto_bind_models: 未找到匹配的模型绑定") + + +def get_rpc_methods(worker_type: str) -> Dict[str, RPCMethod]: + """获取指定 worker 类型的所有 RPC 方法""" + return _RPC_REGISTRY.get(worker_type, {}) + + +def get_all_rpc_methods() -> Dict[str, Dict[str, RPCMethod]]: + """获取所有已注册的 RPC 方法""" + return _RPC_REGISTRY + + +def clear_registry(): + """清空注册表(主要用于测试)""" + _RPC_REGISTRY.clear() + + +# ============================================================ +# 路由配置生成器(从装饰器元数据生成 TOML 兼容的配置) +# ============================================================ + +def generate_route_configs(worker_type: str) -> List[Dict[str, Any]]: + """ + 从装饰器注册表生成路由配置列表 + + 返回的配置与 config/workers//*.toml 格式兼容, + 可用于动态路由注册或生成配置文件。 + """ + methods = get_rpc_methods(worker_type) + configs: List[Dict[str, Any]] = [] + for _key, rpc in methods.items(): + # 从 subject 提取操作名(最后一部分) + op_name = rpc.subject.split(".")[-1] if "." in rpc.subject else rpc.subject + configs.append({ + "subject": rpc.subject, + "path": f"/{op_name}", + "method": rpc.method, + "params": [f"{name}: {_type_to_config_str(pt)}" for name, pt in rpc.params.items()], + "response_model": _build_response_spec(rpc), + "summary": rpc.summary, + "timeout": rpc.timeout, + }) + return configs + + +def _build_response_spec(rpc: RPCMethod) -> Dict[str, Any]: + """从 RPC 方法构建 response_model 配置""" + spec: Dict[str, Any] = {} + + # 优先使用绑定的 response_model + resp_model = rpc.response_model + if resp_model is None: + resp_model = rpc.build_pydantic_response_model() + + if isinstance(resp_model, type) and issubclass(resp_model, BaseModel): + # 遍历 Pydantic 模型字段 + for field_name, field in resp_model.model_fields.items(): + field_type = _type_to_config_str(field.annotation) + required = field.is_required() + default = field.default if not field.is_required() else None + spec[field_name] = { + "type": field_type, + "required": required, + } + if default is not None: + spec[field_name]["default"] = default + else: + spec["result"] = {"type": _type_to_config_str(rpc.return_type), "required": True} + return spec + +``` + +`./utils/worker/src/chongming_worker/runtime.py` +``` +""" +Worker 运行时 — 通用的生命周期管理 + +提供 create_worker / create_workers 工厂函数,供所有 Worker 插件共享。 +""" +import os +import asyncio +import json +import orjson +from typing import Any, Dict, List, Optional + +import zenoh + +from chongming_worker.decorators import ( + RPCMethod, + get_rpc_methods, +) + +async def create_worker( + session: zenoh.Session, + worker_type: str, + worker_id: str, + config: Optional[Dict[str, Any]] = None, + worker_manager=None, +): + """ + 创建一个 Worker 协程 + + 自动发现通过 @rpc_method 注册的端点,为每个 subject 声明 Zenoh Queryable, + 收到请求后按 _method 字段分派到具体方法。 + + Args: + session: Zenoh 会话 + worker_type: Worker 类型标识(匹配 subject 的第一部分) + worker_id: Worker 唯一 ID + config: 自定义配置参数 + worker_manager: WorkerManager 实例(用于本地注册) + """ + print(f"[Worker] {worker_id} 启动 (type={worker_type})") + + # 1. 注册存活令牌 + token = session.liveliness().declare_token(f"worker/{worker_type}/{worker_id}") + + # 2. 注册到本地 WorkerManager(若有) + if worker_manager is not None: + worker_manager.register_worker(worker_type, worker_id) + + # 3. 获取该 worker 类型的所有 RPC 方法 + rpc_methods = get_rpc_methods(worker_type) + if not rpc_methods: + print(f"[Worker] {worker_id} 警告: 未找到任何 RPC 方法") + else: + print(f"[Worker] {worker_id} 发现 {len(rpc_methods)} 个端点:") + for _key, rpc in rpc_methods.items(): + print(f" {rpc.method} {rpc.subject} -> {rpc.name}({', '.join(rpc.params.keys())})") + + # 4. 按 subject 分组(多个方法可共享同一 subject,如 GET + POST) + subject_methods: Dict[str, List[RPCMethod]] = {} + for registry_key, rpc in rpc_methods.items(): + subject_methods.setdefault(rpc.subject, []).append(rpc) + + # 5. 为每个 subject 创建回调函数并声明 Queryable + stop_event = asyncio.Event() + + def make_callback(subject: str, methods: List[RPCMethod]): + """创建处理特定 subject 的回调函数""" + def callback(query: zenoh.Query): + try: + # 解析请求负载 + # payload_str = str(query.payload) if query.payload else "{}" + payload_bytes = bytes(query.payload) if query.payload else b"" + if not payload_bytes: + data = {} + else: + data = orjson.loads(payload_bytes) + http_method = data.get("_method", "GET").upper() + + # 匹配方法(按 HTTP 方法) + matched = next((m for m in methods if m.method.upper() == http_method), None) + if not matched and methods: + matched = methods[0] # 降级到第一个 + if not matched: + query.reply( + key_expr=query.key_expr, + payload=json.dumps({"error": f"No method for {subject}"}).encode("utf-8") + ) + return + + # 调用 RPC 方法(支持同步和异步) + result = asyncio.run(matched.call(data)) + + # 回复 + query.reply( + key_expr=query.key_expr, + payload=orjson.dumps(result) + ) + print(f"[Worker] {worker_id} 回复 {matched.method} {subject}: {result}") + except Exception as e: + error_response = { + "result": 0.0, + "operation": "error", + "error": str(e), + "worker": worker_id + } + query.reply( + key_expr=query.key_expr, + payload=json.dumps(error_response).encode("utf-8") + ) + print(f"[Worker] {worker_id} 异常 {subject}: {e}") + return callback + + # 声明所有 Queryable + queryables: List[zenoh.Queryable] = [] + for subject, methods in subject_methods.items(): + key = f"worker/{worker_type}/{worker_id}/{subject}" + # 使用回调方式声明 + q = session.declare_queryable(key, make_callback(subject, methods)) + queryables.append(q) + print(f"[Worker] {worker_id} Queryable: '{key}' [{', '.join(m.method for m in methods)}]") + + # 6. 保持 worker 存活直到被取消 + try: + await stop_event.wait() + except asyncio.CancelledError: + print(f"[Worker] {worker_id} 正在关闭...") + except Exception as e: + print(f"[Worker] {worker_id} 发生异常: {e}") + finally: + # 清理 + if worker_manager is not None: + worker_manager.unregister_worker(worker_type, worker_id) + token.undeclare() + for q in queryables: + q.undeclare() + print(f"[Worker] {worker_id} 已关闭") + +def create_workers( + session: zenoh.Session, + config: dict, + count: int = 1, + worker_manager=None, +) -> list[asyncio.Task]: + """ + 创建一组 Worker 协程任务 + + 工厂函数,供 WorkerManager 动态加载使用。 + + Args: + session: Zenoh 会话 + config: Worker 配置字典(至少需要 "type" 字段) + count: 启动的 Worker 副本数 + worker_manager: WorkerManager 实例(可选) + + Returns: + list[asyncio.Task]: Worker 协程任务列表 + """ + worker_type = config.get("type", "default") + pid = os.getpid() + tasks = [] + for i in range(count): + worker_id = f"worker-{i}-{pid}" + task = asyncio.create_task( + create_worker( + session=session, + worker_type=worker_type, + worker_id=worker_id, + config=config, + worker_manager=worker_manager, + ) + ) + tasks.append(task) + print(f"[Worker] 已创建任务: {worker_id} (type={worker_type})") + return tasks + + +__all__ = [ + "create_worker", + "create_workers", +] + +``` + +`./utils/worker/src/chongming_worker/config.py` +``` +import sys +from pathlib import Path +from typing import TypedDict, Literal, Optional, cast, List, Dict, Any + +# 根据Python版本选择合适的TOML解析库和Required类型 +if sys.version_info >= (3, 11): + import tomllib + from typing import Required +else: + import tomli as tomllib + from typing_extensions import Required + + +# ============================================================ +# Worker 配置类型 +# ============================================================ + +class WorkerParams(TypedDict, total=False): + """Worker 操作参数定义""" + type: str + required: bool + default: Any + description: str + + +class WorkerResponse(TypedDict, total=False): + """Worker 操作响应定义""" + type: str + required: bool + default: Any + + +class OperationConfig(TypedDict, total=False): + """ + Worker 操作配置结构 + + 对应 config/workers//*.toml 中的每个操作定义。 + """ + name: Required[str] + method: Required[Literal["GET", "POST", "PUT", "DELETE"]] + path: str + subject: Required[str] + summary: str + timeout: float + params: Dict[str, WorkerParams] + response_model: Dict[str, WorkerResponse] + + +class WorkerConfig(TypedDict, total=False): + """ + Worker 配置结构 + + 对应 config/workers//config.toml 中的顶级配置。 + """ + name: Required[str] + type: Required[str] + description: str + version: str + enabled: bool + operations: List[OperationConfig] + + +def load_worker_config(config_dir: Path) -> Dict[str, Any]: + """ + 加载 Worker 配置目录中的所有 TOML 配置文件 + + Args: + config_dir: Worker 配置目录路径,如 Path("config/workers/example") + + Returns: + 解析后的配置字典,包含所有操作的配置 + + Raises: + FileNotFoundError: 当配置目录不存在时抛出异常 + """ + if not config_dir.exists(): + raise FileNotFoundError(f"Worker 配置目录不存在: {config_dir}") + + config: Dict[str, Any] = {"operations": []} + + # 首先加载 config.toml(如果存在) + config_file = config_dir / "config.toml" + if config_file.exists(): + with open(config_file, "rb") as f: + base_config = tomllib.load(f) + config.update(base_config) + + # 然后加载所有 *.toml 中的操作定义 + for toml_file in sorted(config_dir.glob("*.toml")): + if toml_file.name == "config.toml": + continue + with open(toml_file, "rb") as f: + op_data = tomllib.load(f) + # 将文件名(不含扩展名)作为默认操作名 + op_name = toml_file.stem + if "name" not in op_data: + op_data["name"] = op_name + config["operations"].append(op_data) + + return config + + +# ============================================================ +# 旧的配置系统(与主应用 config.py 兼容) +# ============================================================ + +# 设置 env 为 development 或 production + + +class DefaultApp(TypedDict): + """应用默认配置结构""" + + name: str + version: str + description: str + debug: bool + + +class Default(TypedDict): + """系统默认配置结构,包含应用信息、环境、路径等基础配置""" + + app: DefaultApp + env: Literal["production", "development"] + prefix: str + upload_path: str + + +class SqliteConnectionArgs(TypedDict, total=False): + """SQLite数据库连接参数配置""" + + timeout: int + check_same_thread: bool + + +class DatabaseSqlite(TypedDict, total=False): + """SQLite数据库连接池及行为配置""" + + echo: bool + pool_size: int + max_overflow: int + pool_timeout: int + pool_recycle: int + pool_pre_ping: bool + connect_args: SqliteConnectionArgs + + +class Database(TypedDict, total=False): + """数据库配置结构,支持SQLite、MySQL、PostgreSQL三种数据库类型""" + + type: Required[Literal["sqlite", "mysql", "postgresql"]] + url: Required[str] + generate_schemas: bool + sqlite: DatabaseSqlite + mysql: dict + postgresql: dict + database_path: str + + +class Security(TypedDict): + """安全相关配置,包括JWT令牌和安全策略设置""" + + secret_key: str + algorithm: Literal["HS256"] + access_token_expire_minutes: int + refresh_token_expire_days: int + max_sessions_per_user: int + + +class Server(TypedDict, total=False): + """服务器运行配置,包含主机、端口、工作进程等设置""" + + host: Required[str] + port: Required[int] + reload: bool + workers: int + access_log: bool + + +class Logging(TypedDict): + """日志系统配置,定义日志级别、输出格式和文件管理策略""" + + level: str + file: str + format: str + max_size: int + backup_count: int + + +class Cors(TypedDict, total=False): + """跨域资源共享(CORS)配置""" + + allow_origins: List[str] + allow_credentials: bool + allow_methods: List[str] + allow_headers: List[str] + + +class ModuleSystem(TypedDict, total=False): + """模块系统配置,定义模块加载类型和路径""" + + type: str + path: List[str] + + +class FileSystem(TypedDict, total=False): + """文件系统配置,定义存储类型和路径""" + + type: str + path: str + + +class Env(TypedDict, total=False): + """环境特定配置结构,用于开发和生产环境的差异化配置""" + + debug: Required[bool] + server: Required[Server] + logging: Required[Logging] + cors: Cors + module_system: ModuleSystem + file_system: FileSystem + + +class Scheduler(TypedDict): + """任务调度器配置""" + + job_store_path: str + + +class Cache(TypedDict): + """缓存系统配置""" + + cache_store_path: str + + +class Config(TypedDict): + """完整配置结构,整合所有配置项,包含默认配置、数据库、调度器、缓存、安全和环境配置""" + + default: Default + database: Database + scheduler: Scheduler + cache: Cache + security: Security + development: Env + production: Env + + +def load_config(config_path: Path = Path("config.toml")) -> Config: + """ + 从TOML配置文件加载配置数据 + + Args: + config_path: 配置文件路径,默认为当前目录下的config.toml + + Returns: + Config: 解析后的配置字典对象 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + """ + if not config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {config_path}") + with open(config_path, "rb") as f: + return cast(Config, tomllib.load(f)) + +``` + +`./utils/worker/src/chongming_worker/__init__.py` +``` +""" +chongming_worker - 通用 Worker 框架 + +提供 Zenoh RPC 端点的装饰器定义、生命周期管理和配置管理。 +""" +from chongming_worker.decorators import ( + RPCMethod, + rpc_method, + bind_model, + auto_bind_models, + get_rpc_methods, + get_all_rpc_methods, + clear_registry, + generate_route_configs, +) +from chongming_worker.config import ( + load_worker_config, + WorkerConfig, + OperationConfig, +) +from chongming_worker.runtime import ( + create_worker, + create_workers, +) + +__all__ = [ + "RPCMethod", + "rpc_method", + "bind_model", + "auto_bind_models", + "get_rpc_methods", + "get_all_rpc_methods", + "clear_registry", + "generate_route_configs", + "load_worker_config", + "WorkerConfig", + "OperationConfig", + "create_worker", + "create_workers", +] + +``` + +`./utils/request/pyproject.toml` +``` +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +name = "chongming-request" +version = "0.1.0" +description = "worker direct communication" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming_request"] + + +``` + +`./utils/request/README.md` +``` + +``` + +`./utils/request/src/chongming_request/__init__.py` +``` + +``` + +`./public/config.toml` +``` +[default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" +app.name = "chongming" +app.version = "0.1.0" +env = "production" +prefix = "/api/v1" +upload_path = "uploads" + +[database] +generate_schemas = true # 生产环境使用迁移工具 +type = "sqlite" +url = "sqlite+aiosqlite:///./data/database.db" + +[database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 +echo = false +echo_pool = false +max_overflow = 10 +pool_pre_ping = true +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 + +[scheduler] +job_store_path = "scheduler_jobs.db" + +[cache] +cache_store_path = "./" + +[security] +access_token_expire_minutes = 30 +algorithm = "HS256" +max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "your-secret-key-change-in-production" + +[production] +debug = false +env = "production" + +[production.server] +access_log = false +host = "0.0.0.0" +port = 8000 +reload = false +timeout_keep_alive = 65 +workers = 4 + +[production.logging] +backup_count = 5 # 最多保存 10 个日志文件 +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "INFO" +max_size = 10485760 # 10MB + +[production.cors] +allow_credentials = true +allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["http://192.168.137.5:8000", "http://localhost:8000", "http://127.0.0.1:8000"] + +[production.module_system] +path = [ + "resources/app.mbank", + "resources/plugins.mbank", +] +type = "module-bank" + +[production.file_system] +path = "static.svfs" +type = "svfs" + +[[workers]] +type = "example" +module = "plugins.example" +factory = "create_workers" +count = 3 +config = { type = "example" } + +``` + +`./public/server.py` +``` +# server.py +import sys +from utils.launch import init_module_bank +from utils.config import ConfigLoader + +# 加载配置文件并初始化模块银行 +config = ConfigLoader("config.toml") +init_module_bank(config) + +# 从数据库动态导入 app 模块 +# 该模块存储在 SQLite 数据库中,通过自定义导入机制加载 +try: + import app # type: ignore + + app_instance = app.app +except ImportError as e: + print(f"无法从数据库导入 app 模块: {e}") + sys.exit(1) + +# 暴露 ASGI 应用实例,供 uvicorn 服务器使用 +app = app_instance + +``` + +`./public/main.py` +``` +# main.py - 修复 reload 问题 +#!/usr/bin/env python3 +""" +FastAPI chongming 示例 +主入口文件,适配 chongming 打包系统 +""" +import os +import sys +import platform +from utils.launch import launch +from multiprocessing import freeze_support +import server + +use_shell = platform.system() == "Windows" + + +def app_run(app_config: dict, default_config: dict): + """ + 应用运行函数 - 适配模块银行环境 + + Args: + app_config: 应用配置字典,包含服务器配置、环境设置等 + default_config: 默认配置字典,包含应用名称、版本等基础信息 + + Returns: + None: 此函数不返回值,直接启动服务器并阻塞运行 + """ + from app.core.logger import get_logger # type: ignore + + logger = get_logger("app") + + # 获取服务器配置 + server_config = app_config.get("server", {}) + host = server_config.get("host", "0.0.0.0") + port = server_config.get("port", 8000) + timeout_keep_alive = server_config.get("timeout_keep_alive", 5) + + # 打包环境强制禁用重载 + is_frozen = getattr(sys, "frozen", False) + reload = False if is_frozen else server_config.get("reload", False) + # 支持通过环境变量 APP_WORKERS 覆盖 worker 数量(方便嵌入式设备调优) + default_workers = server_config.get("workers", 4) + workers = int(os.environ.get("APP_WORKERS", str(default_workers))) + + # 显示启动信息 + env = app_config.get("env", "development") + logger.info("=" * 50) + logger.info(f"🚀 启动 {default_config.get('app.name', 'FastAPI 应用')}") + logger.info(f"📦 版本: {default_config.get('app.version', '1.0.0')}") + logger.info(f"🌍 环境: {env}") + logger.info(f"📍 地址: http://{host}:{port}") + logger.info(f"📚 文档: http://{host}:{port}/docs") + logger.info(f"🔧 调试: {'启用' if app_config.get('debug', False) else '禁用'}") + logger.info(f"🔄 重载: {'启用' if reload else '禁用'}") + logger.info(f"👥 Workers: {workers}") + logger.info("=" * 50) + + # 根据操作系统选择不同的服务器启动方式 + if use_shell: + import uvicorn + + uvicorn.run( + "server:app", + host=host, + port=port, + reload=reload, + workers=workers, + log_level="debug" if app_config.get("debug", False) else "info", + access_log=False, + timeout_keep_alive=timeout_keep_alive, + ) + else: + from gunicorn.app.wsgiapp import run + + sys.argv = [ + "gunicorn", + "server:app", + "-k", + "uvicorn.workers.UvicornWorker", + "-w", + str(workers), + "-b", + f"{host}:{port}", + ] + run() + + +def main(): + """ + 主函数入口 - 初始化多进程支持并启动应用 + + Returns: + None: 此函数不返回值,调用launch函数启动应用 + """ + freeze_support() + launch(app_run, "production") + + +if __name__ == "__main__": + main() + +``` + +`./public/utils/config.py` +``` +# config_loader.py - 读取config.toml的示例代码 + + +import os +import sys +from pathlib import Path +from typing import Any, Dict, Optional + +# 根据Python版本选择合适的TOML解析库 +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + + +class ConfigLoader: + """TOML配置文件加载器 + + 提供配置文件的加载、访问和管理功能。支持自动查找配置文件、 + 点号分隔的嵌套键访问、配置节获取以及配置重载等功能。 + + Attributes: + config_path: 配置文件的路径 + _config: 存储解析后的配置数据 + """ + + def __init__(self, config_path: Optional[str] = None): + """ + 初始化配置加载器 + + Args: + config_path: 配置文件路径,如果为None则自动查找 + + Raises: + FileNotFoundError: 当无法找到配置文件时抛出异常 + """ + if config_path is None: + # 自动查找配置文件 + config_path = self._find_config_file() + + self.config_path = Path(config_path) + self._config: Dict[str, Any] = {} + self.load() + + def _find_config_file(self) -> str: + """ + 自动查找配置文件 + + 按照预定义的优先级顺序搜索配置文件: + 1. 当前目录下的config.toml + 2. config子目录下的config.toml + 3. 用户配置目录~/.config/myapp/config.toml + 4. 系统配置目录/etc/myapp/config.toml + + Returns: + str: 找到的第一个配置文件路径 + + Raises: + FileNotFoundError: 当所有可能的位置都未找到配置文件时抛出异常 + """ + possible_paths = [ + "config.toml", + "config/config.toml", + "~/.config/myapp/config.toml", + "/etc/myapp/config.toml", + ] + + for path in possible_paths: + expanded_path = os.path.expanduser(path) + if os.path.exists(expanded_path): + return expanded_path + + raise FileNotFoundError("未找到配置文件") + + def load(self) -> None: + """ + 加载配置文件 + + 读取并解析TOML格式的配置文件,将配置数据存储到内部字典中。 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + tomllib.TOMLDecodeError: 当配置文件格式不正确时抛出异常 + """ + if not self.config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {self.config_path}") + + with open(self.config_path, "r", encoding="utf-8") as f: + self._config = tomllib.loads(f.read()) + + def get(self, key: str, default: Any = None) -> Any: + """ + 获取配置值,支持点号分隔的路径 + + 通过点号分隔的键路径访问嵌套的配置值。例如: + - "database.host" 访问 {"database": {"host": "localhost"}} + - "server.port" 访问 {"server": {"port": 8080}} + + Args: + key: 配置键,支持点号分隔的嵌套路径 + default: 当键不存在时返回的默认值,默认为None + + Returns: + Any: 配置值,如果键不存在则返回默认值 + """ + keys = key.split(".") + value = self._config + + try: + for k in keys: + value = value[k] + return value + except (KeyError, TypeError): + return default + + def get_section(self, section: str) -> Dict[str, Any]: + """ + 获取整个配置节 + + 返回指定配置节的完整字典内容,适用于需要访问某个配置节下 + 所有配置项的场景。 + + Args: + section: 配置节名称 + + Returns: + Dict[str, Any]: 配置节的内容字典,如果节不存在则返回空字典 + """ + return self._config.get(section, {}) + + def reload(self) -> None: + """ + 重新加载配置文件 + + 从磁盘重新读取配置文件并更新内部配置数据。 + 适用于配置文件在运行时被修改的场景。 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + """ + self.load() + + def __getitem__(self, key: str) -> Any: + """ + 支持字典式访问 + + 允许使用方括号语法访问配置值,如:config["database.host"] + + Args: + key: 配置键 + + Returns: + Any: 配置值 + + Raises: + KeyError: 当键不存在且无默认值时抛出异常(通过get方法处理) + """ + return self.get(key) + + def __contains__(self, key: str) -> bool: + """ + 检查配置是否存在 + + 支持使用in操作符检查配置键是否存在,如:"database.host" in config + + Args: + key: 要检查的配置键 + + Returns: + bool: 如果配置键存在且值不为None则返回True,否则返回False + """ + return self.get(key) is not None + + @property + def all(self) -> Dict[str, Any]: + """ + 获取所有配置 + + 返回完整的配置字典,适用于需要遍历或导出所有配置的场景。 + + Returns: + Dict[str, Any]: 包含所有配置的字典 + """ + return self._config + +``` + +`./public/utils/__init__.py` +``` + +``` + +`./public/utils/launch.py` +``` +from .config import ConfigLoader +import os +from module_bank import PythonToSQLite +from typing import Callable + + + +def init_module_bank(config): + """ + 初始化模块银行系统,将指定路径下的Python模块打包并安装导入器。 + + 该函数会验证所有模块路径的有效性,创建PythonToSQLite打包器实例, + 列出所有可用模块,并为每个打包器安装导入器以支持模块的动态加载。 + + Args: + config (dict): 配置字典,必须包含 'production' -> 'module_system' -> 'path' 键, + 其值为模块路径列表 + + Raises: + ValueError: 当配置的模块路径中有任何一个不存在时抛出异常 + """ + MODULE_BANK_PATH_LIST = config["production"]["module_system"]["path"] + for module_path in MODULE_BANK_PATH_LIST: + if not os.path.exists(module_path): + raise ValueError(f"模块路径不存在: {module_path}") + + # 为每个模块路径创建PythonToSQLite打包器实例 + packer_list = [ + PythonToSQLite(module_path) for module_path in MODULE_BANK_PATH_LIST + ] + # 遍历所有打包器,列出模块信息并安装导入器 + for packer in packer_list: + key = packer.get_key() + modules = packer.list_modules() + for module in modules: + print(f"{module['module_name']} {'[包]' if module['is_package'] else ''}") + packer.install_importer(key=key) + + +def launch(run_fun: Callable[[dict, dict], None], run_mode: str): + """ + 启动应用程序,加载配置并执行指定的运行函数。 + + 该函数从config.toml文件中加载配置,根据运行模式获取对应的应用配置和默认配置, + 然后调用传入的运行函数执行应用程序逻辑。 + + Args: + run_fun (Callable[[dict, dict], None]): 运行函数,接收两个参数: + - app_config (dict): 当前运行模式的配置 + - default_config (dict): 默认配置 + run_mode (str): 运行模式,用于从配置文件中获取对应模式的配置项 + + Returns: + None + """ + config = ConfigLoader("config.toml") + + # 获取对应运行模式的配置 + default_config = config.get("default", {}) + app_config = config.get(run_mode, {}) + + run_fun(app_config, default_config) + +``` + +`./data/storage/chongming/.nanio-meta/string/95e51f17-ad95-48d9-a349-e0fe5deba291/k8s部署文档.md.json` +``` +{"etag":"\"99bb1226df2b46c13a8b2f099ecb4778\"","size":5756,"last_modified":"2026-07-16T09:55:58.795584+00:00","content_type":"text/markdown","user_metadata":{},"storage_class":"STANDARD","content_encoding":null,"content_disposition":null,"cache_control":null} +``` + +`./data/storage/chongming/.nanio-meta/test/1f7f0e0e-c347-489c-bfb9-1f3e8083303e/k8s部署文档.md.json` +``` +{"etag":"\"99bb1226df2b46c13a8b2f099ecb4778\"","size":5756,"last_modified":"2026-07-16T09:51:00.518358+00:00","content_type":"text/markdown","user_metadata":{},"storage_class":"STANDARD","content_encoding":null,"content_disposition":null,"cache_control":null} +``` + +`./data/storage/chongming/.nanio-meta/test/c6b0c0db-c681-4d44-b56a-350366a03866/test.mp4.json` +``` +{"etag":"\"cdbfb2788ef3025530e985b2c9ddb850\"","size":918742,"last_modified":"2026-07-16T01:36:13.643254+00:00","content_type":"application/octet-stream","user_metadata":{},"storage_class":"STANDARD","content_encoding":null,"content_disposition":null,"cache_control":null} +``` + +`./data/storage/chongming/.nanio-meta/test/e4269914-e2d1-48c1-875e-b114295943c3/test.mp4.json` +``` +{"etag":"\"cdbfb2788ef3025530e985b2c9ddb850\"","size":918742,"last_modified":"2026-07-16T01:34:26.862901+00:00","content_type":"application/octet-stream","user_metadata":{},"storage_class":"STANDARD","content_encoding":null,"content_disposition":null,"cache_control":null} +``` + +`./data/storage/chongming/string/95e51f17-ad95-48d9-a349-e0fe5deba291/k8s部署文档.md` +``` +# Kubernetes 部署 + +## 使用 containerd 配置 Kubernetes + +自Kubernetes 1.24 版本起,不再支持 docker 作为容器运行时,因此 1.24 及以后版本默认使 用containerd 作为容器。 + +推荐使用1.28 及以上Kubernetes,本例程中以 1.28 版本为例。 + +## 安装 containerd + +本节操作需要在所有节点上进行,包括Master节点与Node节点。 + +1. 卸载设备上原有containerd + + ```shell + sudo apt-get remove containerd + sudo apt-get remove containerd.io + sudo apt-get remove runc + ``` + +2. 更新 apt 包索引 + + ```shell + sudo apt-get update + sudo apt-get install ca-certificates curl gnupg lsb-release + ``` + + 添加官方GPG密钥: + + ``` + sudo mkdir -p /etc/apt/keyrings + curl-fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg--dearmor-o /etc/apt/ + →keyrings/docker.gpg + ``` + + 置存储库: + + ``` + echo "deb [arch=$(dpkg--print-architecture) signedby=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release-cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + ``` + +3. 安装 containerd + + 安装最新版本 containerd + + ```shell + sudo apt-get update + sudo apt-get install-y containerd.io + ``` + +## 安装 Kubernetes + +本节操作需要在所有节点上进行,包括Master节点与Node节点 + +1. 卸载设备上已安装Kubernetes + + ```shell + sudo apt-get autoremove kube* + ``` + +2. 更新 apt 包索引并安装 Kubernetes 依赖: + + ```shell + sudo apt-get update + sudo apt-get install -y apt-transport-https ca-certificates curl + ``` + +3. 下载密钥 + + ```shell + sudo curl-fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg--dearmor-o /etc/apt/keyrings/kubernetes-apt-keyring.gpg + ``` + +4. 添加 Kubernetes 仓库 + + ```shell + sudo echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list + ``` + +5. 更新 apt 包索引,并安装 Kubernetes 管理工具 + + ```shell + sudo apt-get update + ``` + + 使用以下指令查看当前可用kubectl 版本 + + ```shell + sudo apt-cache madison kubectl + ``` + + 安装特定版本的 kubectl,kubeadm,kubectl + + ```shell + sudo apt-get install-y kubelet=1.28.0-1.1 kubeadm=1.28.0-1.1 kubectl=1.28.0-1.1 + ``` + +6. 锁定 Kubernetes 版本 + + ```shell + sudo apt-mark hold kubelet kubeadm kubectl + ``` + +## 主节点 + +主节点初始化 + +1. 关闭 swap 分区 + + ```shell + sudo su + swapoff -a + free -m + ``` + +2. 关闭 SELinux + + ```shell + setenforce 0 + ``` + +3. 加载必要的内核模块并设置 sysctl 参数 + + ```shell + modprobe overlay + modprobe br_netfilter + ``` + + 确保模块开机自动加载: + + ```shell + cat < /dev/null + ``` + +3. 安装 containerd + + 安装最新版本 containerd + + ```shell + sudo apt-get update + sudo apt-get install-y containerd.io + ``` + +## 安装 Kubernetes + +本节操作需要在所有节点上进行,包括Master节点与Node节点 + +1. 卸载设备上已安装Kubernetes + + ```shell + sudo apt-get autoremove kube* + ``` + +2. 更新 apt 包索引并安装 Kubernetes 依赖: + + ```shell + sudo apt-get update + sudo apt-get install -y apt-transport-https ca-certificates curl + ``` + +3. 下载密钥 + + ```shell + sudo curl-fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg--dearmor-o /etc/apt/keyrings/kubernetes-apt-keyring.gpg + ``` + +4. 添加 Kubernetes 仓库 + + ```shell + sudo echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list + ``` + +5. 更新 apt 包索引,并安装 Kubernetes 管理工具 + + ```shell + sudo apt-get update + ``` + + 使用以下指令查看当前可用kubectl 版本 + + ```shell + sudo apt-cache madison kubectl + ``` + + 安装特定版本的 kubectl,kubeadm,kubectl + + ```shell + sudo apt-get install-y kubelet=1.28.0-1.1 kubeadm=1.28.0-1.1 kubectl=1.28.0-1.1 + ``` + +6. 锁定 Kubernetes 版本 + + ```shell + sudo apt-mark hold kubelet kubeadm kubectl + ``` + +## 主节点 + +主节点初始化 + +1. 关闭 swap 分区 + + ```shell + sudo su + swapoff -a + free -m + ``` + +2. 关闭 SELinux + + ```shell + setenforce 0 + ``` + +3. 加载必要的内核模块并设置 sysctl 参数 + + ```shell + modprobe overlay + modprobe br_netfilter + ``` + + 确保模块开机自动加载: + + ```shell + cat <{Request|Response} + (如 AddGetRequest, AddGetResponse 由 gen_models 自动生成) + - 装饰器 subject 中的操作名 → 模型前缀(驼峰) + example.add → Add, example.subtract → Subtract +""" +import time + +from chongming_worker import rpc_method, auto_bind_models +from chongming_worker.runtime import create_workers as _create_workers + +# ============================================================ +# 显式导入 Pydantic 模型(避免 PyArmor 打包后 importlib 动态导入异常) +# ============================================================ +from .models.pydantic import ( + AddGetRequest, + AddGetResponse, + AddPostRequest, + AddPostResponse, + SubtractGetRequest, + SubtractGetResponse, + MultiplyGetRequest, + MultiplyGetResponse, + DivideGetRequest, + DivideGetResponse, +) + + +# ============================================================ +# 注册 RPC 端点 +# ============================================================ +# 直接在 @rpc_method 中指定 response_model(和可选的 request_model), +# 不再依赖 auto_bind_models 进行动态导入绑定。 +# 这样 PyArmor 打包后不会因 importlib.import_module 路径解析失败而出问题。 + + +@rpc_method( + subject="example.add", + method="GET", + summary="Add two numbers via GET", + response_model=AddGetResponse, +) +def add(add_get_request:AddGetRequest) -> AddGetResponse: + """加法运算(GET)— 通过查询参数传入 a、b,返回它们的和""" + return AddGetResponse( + result=add_get_request.a + add_get_request.b, + operation="add", + timestamp=time.time() + ) + +@rpc_method( + subject="example.add", + method="POST", + summary="Add two numbers via POST", + response_model=AddPostResponse, +) +def add_post(add_post_request: AddPostRequest) -> AddPostResponse: + """加法运算(POST)— 通过 JSON Body 传入 a、b,返回它们的和""" + return AddPostResponse( + result=add_post_request.a + add_post_request.b, + operation="add", + timestamp=time.time(), + ) + + +@rpc_method( + subject="example.subtract", + method="GET", + summary="Subtract two numbers", + response_model=SubtractGetResponse, +) +def subtract(subtract_get_request: SubtractGetRequest) -> SubtractGetResponse: + """减法运算:a - b""" + return SubtractGetResponse( + result=subtract_get_request.a - subtract_get_request.b, + operation="subtract", + timestamp=time.time(), + ) + + +@rpc_method( + subject="example.multiply", + method="GET", + summary="Multiply two numbers", + response_model=MultiplyGetResponse, +) +def multiply(multiply_get_request=MultiplyGetRequest) -> MultiplyGetResponse: + """乘法运算:a * b""" + return MultiplyGetResponse( + result=multiply_get_request.a * multiply_get_request.b, + operation="multiply", + timestamp=time.time(), + ) + +@rpc_method( + subject="example.divide", + method="GET", + summary="Divide two numbers", + response_model=DivideGetResponse, +) +def divide(divide_get_request: DivideGetRequest) -> DivideGetResponse: + """除法运算:a / b(b 不能为 0)""" + if divide_get_request.b == 0: + return DivideGetResponse( + error="除数不能为零", + operation="divide", + result=0.0, + timestamp=time.time(), + ) + return DivideGetResponse( + result=divide_get_request.a / divide_get_request.b, + operation="divide", + timestamp=time.time(), + ) + +# ============================================================ +# 自动绑定 Pydantic 模型(作为备用,但在显式绑定后实际不再需要) +# ============================================================ +# 保留 auto_bind_models 调用作为 fallback,确保任何未显式指定模型的端点 +# 也能自动匹配到合适的模型。由于大部分端点已在 @rpc_method 中显式指定, +# auto_bind_models 会跳过已绑定的端点。 +auto_bind_models("example") + + +# ============================================================ +# 工厂函数 — 供 app lifespan 动态加载 +# ============================================================ +# 框架通过 __import__ + getattr 获取此函数, +# 然后调用 create_workers(session, config, count, worker_manager=worker_manager)。 +# 详见 src/chongming/app/__init__.py 的 lifespan。 + + +def create_workers(session, config, count=1, worker_manager=None): + """插件入口 — 委托给 chongming_worker 运行时创建 Worker 协程""" + return _create_workers(session, config, count, worker_manager) + +``` + +`./src/plugins/example/models/__init__.py` +``` +""" +模型导出 — example +自动生成,请勿手动修改 +""" + +from .pydantic import AddGetRequest +from .pydantic import AddGetResponse +from .pydantic import AddPostRequest +from .pydantic import AddPostResponse +from .pydantic import DivideGetRequest +from .pydantic import DivideGetResponse +from .pydantic import ExampleResponse +from .pydantic import MultiplyGetRequest +from .pydantic import MultiplyGetResponse +from .pydantic import SubtractGetRequest +from .pydantic import SubtractGetResponse + +__all__ = [ + "AddGetRequest", + "AddGetResponse", + "AddPostRequest", + "AddPostResponse", + "DivideGetRequest", + "DivideGetResponse", + "ExampleResponse", + "MultiplyGetRequest", + "MultiplyGetResponse", + "SubtractGetRequest", + "SubtractGetResponse", +] + +``` + +`./src/plugins/example/models/pydantic.py` +``` +""" +自动生成的 Pydantic 模型 — example +来源: config/workers/example/ +""" + +from __future__ import annotations + +from typing import Any, Optional +from pydantic import BaseModel, Field + +class AddGetRequest(BaseModel): + """Add two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class AddGetResponse(BaseModel): + """Add two numbers — 响应体""" + result: float = Field(...) + operation: str = Field(default='add') + timestamp: Optional[float] = Field(default=0.0) + +class AddPostRequest(BaseModel): + """Add two numbers (POST) — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class AddPostResponse(BaseModel): + """Add two numbers (POST) — 响应体""" + result: float = Field(...) + operation: str = Field(default='add') + timestamp: Optional[float] = Field(default=0.0) + +class DivideGetRequest(BaseModel): + """Divide two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class DivideGetResponse(BaseModel): + """Divide two numbers — 响应体""" + result: Optional[float] = Field(default=0.0) + operation: str = Field(default='divide') + timestamp: Optional[float] = Field(default=0.0) + error: Optional[str] = Field(default='') + +class MultiplyGetRequest(BaseModel): + """Multiply two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class MultiplyGetResponse(BaseModel): + """Multiply two numbers — 响应体""" + result: float = Field(...) + operation: str = Field(default='multiply') + timestamp: Optional[float] = Field(default=0.0) + +class SubtractGetRequest(BaseModel): + """Subtract two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class SubtractGetResponse(BaseModel): + """Subtract two numbers — 响应体""" + result: float = Field(...) + operation: str = Field(default='subtract') + timestamp: Optional[float] = Field(default=0.0) + + +# 所有响应模型的联合类型 +ExampleResponse = MultiplyGetResponse | AddGetResponse | SubtractGetResponse | AddPostResponse | DivideGetResponse + +``` + +`./src/chongming/__init__.py` +``` +import uvicorn +from .app.core.config import load_config +from .app.core.logger import get_logger + + +def serve(): + """ + 使用 uvicorn 启动服务 + + 从配置文件中加载服务器配置和日志配置,然后使用 uvicorn 启动应用服务。 + 该函数适用于开发环境或单进程部署场景。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + log_config = config[env]["logging"] + logger = get_logger("app") + logger.info(f"启动服务: {server_config}") + logger.info(f"启动日志: {log_config}") + uvicorn.run("chongming.app:app", **server_config) + + +def gunicorn_serve(): + """ + 使用 gunicorn 启动服务 + + 通过 gunicorn 作为进程管理器启动应用,使用 UvicornWorker 作为 worker 类型, + 支持多进程部署,适用于生产环境。 + + 从配置文件中读取服务器配置,动态设置 gunicorn 的 worker 数量、绑定地址和端口等参数。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + import sys + from gunicorn.app.wsgiapp import run + + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + + # 构建 gunicorn 命令行参数并启动服务 + sys.argv = [ + "gunicorn", + "chongming.app:app", + "-k", + "uvicorn.workers.UvicornWorker", + "-w", + f"{server_config.get('workers', 4)}", + "-b", + f"{server_config.get('host', '0.0.0.0')}:{server_config.get('port', 8000)}", + ] + run() + + +if __name__ == "__main__": + serve() + +``` + +`./src/chongming/scripts/code_content.py` +``` +import os +import sys + + +def generate_markdown_from_py_files(directory, output_file): + with open(output_file, "w", encoding="utf-8") as md_file: + for root, dirs, files in os.walk(directory): + # 排除 venv 目录 + dirs[:] = [d for d in dirs if d != ".venv"] + dirs[:] = [d for d in dirs if d != ".vscode"] + dirs[:] = [d for d in dirs if d != "node_modules"] + dirs[:] = [d for d in dirs if d != "build"] + dirs[:] = [d for d in dirs if d != "dist"] + dirs[:] = [d for d in dirs if d != "chongming-web"] + for file in files: + if ( + file.endswith(".py") + or file.endswith(".rs") + or file.endswith(".toml") + or file.endswith(".md") + or file.endswith(".css") + or file.endswith(".vue") + or file.endswith(".ts") + or file.endswith(".json") + or file.endswith(".html") + or file.endswith(".sh") + or file == "Dockerfile" + ): + file_path = os.path.join(root, file) + if file == "output.md" or file == "package-lock.json": + continue + if "generated" in file_path: + continue + md_file.write(f"`{file_path}`\n") + md_file.write("```\n") + with open(file_path, "r", encoding="utf-8") as py_file: + md_file.write(py_file.read()) + md_file.write("\n```\n\n") + + +if __name__ == "__main__": + # 指定目录和输出文件名 + target_directory = sys.argv[1] # 替换为你的目标目录 + output_markdown_file = "output.md" # 输出的 Markdown 文件名 + + generate_markdown_from_py_files(target_directory, output_markdown_file) + print(f"Markdown 文件已生成:{output_markdown_file}") + +``` + +`./src/chongming/scripts/gen_models.py` +``` +""" +模型生成脚本 — 从 config/workers//*.toml 生成 Pydantic 模型 + +用法: + uv run gen_models config/workers/example src/plugins/example + +工作流: + 1. 编写 config/workers//*.toml 配置文件 + 2. 执行 gen_models 生成 Pydantic 模型 + 3. 在业务代码中 import 生成的模型 + +功能: + 1. 扫描配置目录下的所有 .toml 文件 + 2. 解析每个路由的 params 和 response_model + 3. 生成 Pydantic 请求/响应模型 + 4. 写入 src/plugins//models/__init__.py 和 models/pydantic.py +""" + +import sys +import json +from pathlib import Path +from typing import Any + + +def parse_type_str(type_str: str) -> str: + """将 TOML 中的类型字符串映射为 Python 类型注解""" + mapping = { + "float": "float", + "int": "int", + "str": "str", + "bool": "bool", + "string": "str", + "integer": "int", + "number": "float", + "boolean": "bool", + "any": "Any", + } + + if type_str.startswith("list[") and type_str.endswith("]"): + inner = type_str[5:-1] + inner_py = mapping.get(inner, inner) + return f"list[{inner_py}]" + + if type_str == "dict": + return "dict[str, Any]" + + if type_str.startswith("dict[") and type_str.endswith("]"): + inner = type_str[5:-1] + if "," in inner: + parts = [p.strip() for p in inner.split(",", 1)] + inner_py = mapping.get(parts[-1], parts[-1]) + else: + inner_py = mapping.get(inner, inner) + return f"dict[str, {inner_py}]" + + if type_str.startswith("Optional[") and type_str.endswith("]"): + inner = type_str[9:-1] + inner_py = mapping.get(inner, inner) + return f"Optional[{inner_py}]" + + return mapping.get(type_str, type_str) + + +def parse_params(params: list[str]) -> list[dict[str, str]]: + """解析 params 列表,如 ['a: float', 'b: float']""" + result = [] + for p in params: + if ":" in p: + name, type_str = p.split(":", 1) + result.append({"name": name.strip(), "type": parse_type_str(type_str.strip())}) + else: + result.append({"name": p.strip(), "type": "str"}) + return result + + +def parse_response_model(response_model: dict[str, Any]) -> list[dict[str, Any]]: + """解析 response_model 字典,返回字段列表""" + fields: list[dict[str, Any]] = [] + for field_name, field_spec in response_model.items(): + entry: dict[str, Any] = {"name": field_name} + if isinstance(field_spec, dict): + entry["type"] = parse_type_str(field_spec.get("type", "str")) + entry["required"] = field_spec.get("required", True) + if "default" in field_spec: + entry["default"] = field_spec["default"] + else: + entry["type"] = parse_type_str(str(field_spec)) + entry["required"] = True + fields.append(entry) + return fields + + +def _model_name(base: str, method: str, suffix: str) -> str: + """生成模型名: e.g. add + GET + Request -> AddGetRequest""" + return f"{base.title().replace('-', '').replace('_', '')}{method.title()}{suffix}" + + +def generate_pydantic_code( + plugin_name: str, + routes: list[dict[str, Any]], +) -> str: + """生成 pydantic.py 文件内容""" + lines = [] + lines.append('"""') + lines.append(f"自动生成的 Pydantic 模型 — {plugin_name}") + lines.append(f"来源: config/workers/{plugin_name}/") + lines.append('"""') + lines.append("") + lines.append("from __future__ import annotations") + lines.append("") + lines.append("from typing import Any, Optional") + lines.append("from pydantic import BaseModel, Field") + lines.append("") + + seen_models: set[str] = set() + + for route in routes: + route_name = route["name"] + method = route["method"] + summary = route.get("summary", "") + + # --- Request Model --- + params = route.get("parsed_params", []) + if params: + model_name = _model_name(route_name, method, "Request") + if model_name not in seen_models: + seen_models.add(model_name) + lines.append(f"class {model_name}(BaseModel):") + lines.append(f' """{summary} — 请求体"""') + for p in params: + lines.append(f" {p['name']}: {p['type']} = Field(...)") + lines.append("") + + # --- Response Model --- + response_fields = route.get("parsed_response", []) + if response_fields: + model_name = _model_name(route_name, method, "Response") + if model_name not in seen_models: + seen_models.add(model_name) + lines.append(f"class {model_name}(BaseModel):") + lines.append(f' """{summary} — 响应体"""') + for f in response_fields: + field_type = f["type"] + if f.get("required", True): + if "default" in f: + default_repr = ( + json.dumps(f["default"]) + if not isinstance(f["default"], str) + else f"'{f['default']}'" + ) + lines.append(f" {f['name']}: {field_type} = Field(default={default_repr})") + else: + lines.append(f" {f['name']}: {field_type} = Field(...)") + else: + if "default" in f: + default_repr = ( + json.dumps(f["default"]) + if not isinstance(f["default"], str) + else f"'{f['default']}'" + ) + lines.append(f" {f['name']}: Optional[{field_type}] = Field(default={default_repr})") + else: + lines.append(f" {f['name']}: Optional[{field_type}] = Field(default=None)") + lines.append("") + + # --- Union type for all responses --- + response_models = [m for m in seen_models if m.endswith("Response")] + if response_models: + plugin_title = plugin_name.title().replace("-", "").replace("_", "") + lines.append("") + lines.append("# 所有响应模型的联合类型") + lines.append(f"{plugin_title}Response = " + " | ".join(response_models)) + lines.append("") + + return "\n".join(lines) + + +def generate_init_code( + plugin_name: str, + routes: list[dict[str, Any]], +) -> str: + """生成 __init__.py 文件内容""" + lines = [] + lines.append('"""') + lines.append(f"模型导出 — {plugin_name}") + lines.append("自动生成,请勿手动修改") + lines.append('"""') + lines.append("") + + seen_models: set[str] = set() + for route in routes: + rname = route["name"] + method = route["method"] + if route.get("parsed_params"): + seen_models.add(_model_name(rname, method, "Request")) + if route.get("parsed_response"): + seen_models.add(_model_name(rname, method, "Response")) + + response_models = [m for m in seen_models if m.endswith("Response")] + if response_models: + plugin_title = plugin_name.title().replace("-", "").replace("_", "") + seen_models.add(f"{plugin_title}Response") + + for model_name in sorted(seen_models): + lines.append(f"from .pydantic import {model_name}") + + lines.append("") + lines.append("__all__ = [") + for model_name in sorted(seen_models): + lines.append(f' "{model_name}",') + lines.append("]") + lines.append("") + + return "\n".join(lines) + + +def scan_config_dir(config_dir: Path) -> list[dict[str, Any]]: + """扫描配置目录,解析所有 .toml 文件中的路由定义""" + import tomli + + routes = [] + toml_files = sorted(config_dir.glob("*.toml")) + + group_config = {} + config_file = config_dir / "config.toml" + if config_file.exists(): + with open(config_file, "rb") as f: + group_config = tomli.load(f) + + defaults = group_config.get("worker", {}).get("defaults", {}) + router_prefix = ( + group_config.get("worker", {}) + .get("registration", {}) + .get("router_prefix", "") + ) + + for toml_file in toml_files: + if toml_file.name == "config.toml": + continue + + with open(toml_file, "rb") as f: + data = tomli.load(f) + + route_name = toml_file.stem + + for method in ("GET", "POST", "PUT", "DELETE", "PATCH"): + if method not in data: + continue + + entry = data[method] + params_raw = entry.get("params", []) + response_raw = entry.get("response_model", {}) + + route = { + "name": route_name, + "method": method, + "summary": entry.get("summary", ""), + "docstring": entry.get("docstring", ""), + "params": params_raw, + "response_model": response_raw, + "parsed_params": parse_params(params_raw), + "parsed_response": parse_response_model(response_raw), + "path": entry.get("path", f"{router_prefix}/{route_name}"), + "timeout": entry.get("timeout", defaults.get("timeout", 3.0)), + "auth_required": entry.get("auth_required", defaults.get("auth_required", False)), + } + routes.append(route) + + return routes + + +def main(): + if len(sys.argv) < 3: + print("用法: uv run gen_models ") + print("示例:") + print(" uv run gen_models config/workers/example src/plugins/example") + sys.exit(1) + + config_dir = Path(sys.argv[1]) + plugin_dir = Path(sys.argv[2]) + plugin_name = plugin_dir.name + + if not config_dir.exists(): + print(f"❌ 配置目录不存在: {config_dir}") + sys.exit(1) + if not plugin_dir.exists(): + print(f"❌ 插件目录不存在: {plugin_dir}") + sys.exit(1) + + print(f"🔍 扫描配置目录: {config_dir}") + routes = scan_config_dir(config_dir) + print(f" 发现 {len(routes)} 个路由定义") + + if not routes: + print("⚠️ 未找到路由定义,跳过模型生成") + return + + models_dir = plugin_dir / "models" + models_dir.mkdir(parents=True, exist_ok=True) + + pydantic_code = generate_pydantic_code(plugin_name, routes) + pydantic_file = models_dir / "pydantic.py" + pydantic_file.write_text(pydantic_code, encoding="utf-8") + print(f"✅ 生成: {pydantic_file}") + + init_code = generate_init_code(plugin_name, routes) + init_file = models_dir / "__init__.py" + init_file.write_text(init_code, encoding="utf-8") + print(f"✅ 生成: {init_file}") + + print(f"\n📋 生成的模型:") + for route in routes: + method = route["method"] + name = route["name"] + params = route.get("parsed_params", []) + response = route.get("parsed_response", []) + if params: + req_model = _model_name(name, method, "Request") + param_strs = [f"{p['name']}: {p['type']}" for p in params] + print(f" 📥 {req_model}: {', '.join(param_strs)}") + if response: + resp_model = _model_name(name, method, "Response") + field_strs = [f"{f['name']}: {f['type']}" for f in response] + print(f" 📤 {resp_model}: {', '.join(field_strs)}") + + +def gen_models(): + """CLI 入口""" + main() + + +if __name__ == "__main__": + main() + +``` + +`./src/chongming/scripts/build.py` +``` +""" +构建脚本 - 将 chongming 应用打包为可执行文件 + +功能阶段: + 1. 模块打包(module-bank): 加密打包 Python 模块 + 2. 资源准备: 创建 build 目录, 复制必要文件 + 3. 混淆与打包: pyarmor 混淆 + PyInstaller/Nuitka 打包 + 4. 数据库初始化: 创建表结构和默认数据 + 5. 前端构建: 编译 Vue 项目并打包为 VFS + +用法: + python -m chongming.scripts.build [pyinstaller|nuitka] +""" + +import json +import ast +import platform +import shutil +import subprocess +import sys +import asyncio +import time +from dataclasses import dataclass, field +from pathlib import Path + +from module_bank import PythonToSQLite +from module_bank.encryption import Encryption +from sqlite_vfs.core import SQLiteVFS +from sqlite_vfs.folder_packer import FolderPacker +from ..app.core.config import get_config, Config + +# ── 常量 ──────────────────────────────────────────────────────────────────── + +PYINSTALLER = "pyinstaller" +NUITKA = "nuitka" +MODES = (PYINSTALLER, NUITKA) + +is_windows = platform.system() == "Windows" + +# ── 构建结果数据结构 ───────────────────────────────────────────────────────── + + +@dataclass +class BuildStepResult: + """单个构建步骤的执行结果""" + + name: str # 步骤名称 + success: bool # 是否成功 + duration: float # 耗时(秒) + message: str = "" # 附加信息(如包大小) + + +@dataclass +class BuildReport: + """构建总结报告""" + + mode: str + start_time: float + end_time: float = 0.0 + steps: list[BuildStepResult] = field(default_factory=list) + + @property + def total_duration(self) -> float: + return self.end_time - self.start_time if self.end_time else 0.0 + + @property + def all_success(self) -> bool: + return all(s.success for s in self.steps) + + def add_step(self, name: str, success: bool, duration: float, message: str = ""): + self.steps.append(BuildStepResult(name, success, duration, message)) + + def print_summary(self): + """打印结构化的构建总结""" + sep = "=" * 60 + print(f"\n{sep}") + print(f" 🏗️ CHONGMING 构建报告") + print(f"{sep}") + print(f" 构建模式 : {self.mode}") + print(f" 总耗时 : {self.total_duration:.2f}s") + print(f" 构建状态 : {'✅ 成功' if self.all_success else '❌ 失败'}") + print(f"{sep}") + print(f" 步骤明细:") + for i, step in enumerate(self.steps, 1): + icon = "✅" if step.success else "❌" + msg = f" ({step.message})" if step.message else "" + print( + f" {icon} [{i}/{len(self.steps)}] {step.name:<20s} {step.duration:>7.2f}s{msg}" + ) + print(f"{sep}\n") + + +# ── 工具函数 ──────────────────────────────────────────────────────────────── + + +def _run_cmd( + cmd: list[str], + cwd: str | None = None, + capture: bool = True, + shell: bool | None = None, +) -> subprocess.CompletedProcess: + """统一执行外部命令""" + if shell is None: + shell = is_windows + return subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + shell=shell, + encoding="utf-8", + errors="replace", + ) + + +def _format_size(path: str | Path) -> str: + """获取文件可读大小""" + size = Path(path).stat().st_size + for unit in ("B", "KB", "MB", "GB"): + if size < 1024: + return f"{size:.2f} {unit}" + size /= 1024.0 + return f"{size:.2f} TB" + + +def _ensure_dir(path: str | Path): + """确保目录存在""" + Path(path).mkdir(parents=True, exist_ok=True) + + +def _resolve_mode() -> str: + """解析构建模式""" + mode = PYINSTALLER + if len(sys.argv) >= 2: + mode = sys.argv[1].lower() + if mode not in MODES: + print(f" ❌ 不支持的构建模式: {mode},可选: {', '.join(MODES)}") + sys.exit(1) + return mode + + +# ── 阶段 1: 模块打包 (module-bank) ────────────────────────────────────────── + + +def _stage_pack_modules(config: Config) -> str: + """ + 使用 module-bank 加密打包 Python 模块为 .mbank 文件。 + 返回打包过程中的提示信息。 + """ + _ensure_dir("resources") + mbank_list = config.get("production", {}).get("module_system", {}).get("path", []) + if not mbank_list: + return "⚠️ 未配置 module_system.path,跳过模块打包" + + messages = [] + for output_path, source_dir, package_name in mbank_list: + key = Encryption.generate_key() + packer = PythonToSQLite( + output_path, + key, + pyarmor_obfuscate=True, + ) + packer.pack_directory(source_dir, package_name) + packer.verify_package_structure() + packer.packer_key(key) + packer.delete_source_code(None) + size = _format_size(output_path) + messages.append(f"{Path(output_path).name} ({size})") + return " - " + "\n - ".join(messages) + + +def _stage_prepare_build_dir(config: Config, mode: str) -> str: + """准备 build 目录并复制必要文件""" + build_path = Path("build") + if build_path.exists(): + shutil.rmtree(str(build_path)) + _ensure_dir("build") + + # 复制 utils + utils_src = Path("public/utils") + if utils_src.exists(): + shutil.copytree(utils_src, build_path / "utils") + + # 复制 resources + resources_src = Path("resources") + if resources_src.exists(): + shutil.copytree(resources_src, build_path / "resources") + + resources_src = Path("config") + if resources_src.exists(): + shutil.copytree(resources_src, build_path / "config") + + # 复制核心文件 + for f in ("main.py", "server.py", "config.toml"): + shutil.copy(f"public/{f}", f"build/{f}") + + # 注入 JWT 密钥 + jwt_key = Encryption.generate_key() + cfg_path = build_path / "config.toml" + content = cfg_path.read_text(encoding="utf-8") + content = content.replace("your-secret-key-change-in-production", jwt_key) + cfg_path.write_text(content, encoding="utf-8") + + return "资源准备完成" + + +# ── 阶段 3: PyArmor 混淆 ─────────────────────────────────────────────────── + + +def _stage_pyarmor_obfuscate() -> str: + """对关键脚本执行 PyArmor 混淆""" + files = [ + ("main.py", "./"), + ("server.py", "./"), + ("utils/launch.py", "./utils"), + ("utils/config.py", "./utils"), + ] + failed = [] + for filename, output in files: + result = _run_cmd( + ["pyarmor", "gen", f"--output={output}", filename], + cwd="build", + ) + if result.returncode != 0: + failed.append(filename) + if failed: + return f"❌ 混淆失败: {', '.join(failed)}" + return "✅ 所有文件混淆成功" + + +# ── 阶段 4: PyInstaller 打包 ──────────────────────────────────────────────── + + +def _stage_pyinstaller(config: Config) -> tuple[str, str]: + """执行 PyInstaller 打包,返回 (状态消息, 可执行文件路径)""" + base_imports = config.get("pyinstaller_build_import", {}) + build_imports = list(base_imports.get("base", [])) + build_imports.extend( + base_imports.get("windows", []) if is_windows else base_imports.get("linux", []) + ) + + icon = Path("../../public/chongming.ico") + cmd = [ + sys.executable, + "-m", + "PyInstaller", + "--onefile", + "--noconfirm", + "--optimize", + "2", + "--name", + "chongming", + "--distpath", + ".", + "--workpath", + "./build_temp", + "--specpath", + "./specs", + "--clean", + ] + if not is_windows: + cmd.append("--strip") + if icon.exists(): + cmd.extend(["--icon", str(icon)]) + for imp in build_imports: + cmd.extend(["--hidden-import", imp]) + + cmd.append("main.py") + + result = _run_cmd(cmd, cwd="build", capture=True) + if result.returncode != 0: + return f"❌ PyInstaller 失败", "" + + exe_path = Path("build/chongming") + if is_windows: + exe_path = exe_path.with_suffix(".exe") + + suffix = ".exe" if is_windows else "" + exe = Path("build") / f"chongming{suffix}" + if exe.exists(): + size = _format_size(exe) + return f"✅ PyInstaller 打包成功 ({size})", str(exe) + return "✅ PyInstaller 打包完成 (但未找到可执行文件)", "" + + +# ── 阶段 5: Nuitka 打包 ──────────────────────────────────────────────────── + + +def _stage_nuitka(config: Config) -> tuple[str, str]: + """执行 Nuitka 打包,返回 (状态消息, 可执行文件路径)""" + base_imports = config.get("pyinstaller_build_import", {}).get("base", []) + extra = config.get("pyinstaller_build_import", {}).get( + "windows" if is_windows else "linux", [] + ) + + packages = [ + "module_bank", + "sqlite_vfs", + "fastapi", + "uvicorn", + "gunicorn", + "diskcache", + "apscheduler", + "tomli", + "typing_extensions", + ] + + cmd = [ + sys.executable, + "-m", + "nuitka", + "--standalone", + "--onefile", + "--follow-imports", + "--static-libpython=no", + "--remove-output", + "--assume-yes-for-downloads", + "--lto=yes", + "--disable-plugin=tk-inter", + "--disable-plugin=numpy", + "--output-dir=.", + "--output-filename=chongming", + "main.py", + "--jobs=2", + ] + if shutil.which("upx"): + cmd.append("--enable-plugin=upx") + for pkg in packages: + cmd.append(f"--include-package={pkg}") + for mod in list(base_imports) + extra: + cmd.append(f"--include-module={mod}") + + if is_windows: + icon = Path("../../public/chongming.ico") + if icon.exists(): + cmd.append(f"--windows-icon-from-ico={icon}") + + try: + _run_cmd(cmd, cwd="build", capture=False) + except subprocess.CalledProcessError: + # 降级重试(移除可能不兼容的参数) + fallback = [ + arg for arg in cmd if arg not in ("--lto=yes", "--enable-plugin=upx") + ] + _run_cmd(fallback, cwd="build", capture=False) + + exe = Path("build/chongming") + if exe.exists(): + # 后处理瘦身 + if not is_windows: + subprocess.run(["strip", str(exe)], check=False) + if shutil.which("upx"): + subprocess.run(["upx", "--best", str(exe)], check=False) + size = _format_size(exe) + return f"✅ Nuitka 编译完成 ({size})", str(exe) + + return "❌ Nuitka 编译失败 (未生成可执行文件)", "" + + +# ── 阶段 6: 清理临时文件 ──────────────────────────────────────────────────── + + +def _stage_cleanup(mode: str) -> str: + """清理构建过程中的临时文件""" + build = Path("build") + dirs_to_remove = [ + "build_temp", + "specs", + "utils", + "pyarmor_runtime_000000", + "__pycache__", + ] + files_to_remove = ["main.py", "server.py"] + + for d in dirs_to_remove: + target = build / d + if target.exists(): + shutil.rmtree(str(target), ignore_errors=True) + + for f in files_to_remove: + target = build / f + if target.exists(): + target.unlink(missing_ok=True) + + return "临时文件已清理" + +# ── 阶段 8: Vue 前端构建 ──────────────────────────────────────────────────── + + +def _stage_build_vue() -> str: + """构建 Vue 前端并打包为 SQLite VFS""" + dist_dir = "./src/chongming-web/dist" + output_db = "./build/static.svfs" + fs_name = "frontend" + + # 1. npm build + result = _run_cmd( + ["npm", "run", "build"], + cwd="./src/chongming-web", + capture=True, + ) + if result.returncode != 0: + err = (result.stderr or "").strip()[:200] + return f"❌ Vue 构建失败: {err}" + + # 2. 检查产物 + if not Path(dist_dir).exists(): + return "❌ Vue 构建成功但未找到 dist 目录" + + # 3. 打包为 VFS + vfs = SQLiteVFS(output_db, compress=False) + packer = FolderPacker(sqlite_vfs=vfs, exclude_patterns=[], fs_name=fs_name) + try: + packer.pack_folder(dist_dir) + finally: + vfs.close() + + size = _format_size(output_db) + return f"✅ Vue 打包完成 ({size})" + + +# ── 主流程 ────────────────────────────────────────────────────────────────── + + +async def main(): + """规范化构建主流程""" + mode = _resolve_mode() + report = BuildReport(mode=mode, start_time=time.time()) + config = get_config() + + async def run_step(name: str, fn, *args, **kwargs) -> str: + """执行一个构建步骤并记录结果(支持同步和异步函数)""" + start = time.time() + try: + result = fn(*args, **kwargs) + if asyncio.iscoroutine(result): + msg = await result + else: + msg = result + elapsed = time.time() - start + report.add_step(name, success=True, duration=elapsed, message=msg or "") + print(f" ✅ {name:<25s} {elapsed:>7.2f}s {msg}") + return msg + except Exception as e: + elapsed = time.time() - start + err_msg = str(e).strip()[:200] + report.add_step(name, success=False, duration=elapsed, message=err_msg) + print(f" ❌ {name:<25s} {elapsed:>7.2f}s {err_msg}") + return "" + + print("\n" + "=" * 60) + print(" 🏗️ CHONGMING 构建开始") + print(f" 模式: {mode} | 平台: {platform.system()}") + print("=" * 60 + "\n") + + # 阶段 1: 模块打包 + await run_step("模块打包(module-bank)", _stage_pack_modules, config) + + # 阶段 2: 准备构建目录 + await run_step("准备构建目录", _stage_prepare_build_dir, config, mode) + + # 阶段 3+4: 混淆 + 打包 + if mode == PYINSTALLER: + await run_step("PyArmor 混淆", _stage_pyarmor_obfuscate) + start = time.time() + try: + msg, exe_path = _stage_pyinstaller(config) + elapsed = time.time() - start + success = "❌" not in msg + report.add_step( + "PyInstaller 打包", success, elapsed, msg.replace("✅ PyInstaller ", "") + ) + print( + f" {'✅' if success else '❌'} PyInstaller 打包{' ' * 12} {elapsed:>7.2f}s {msg}" + ) + except Exception as e: + elapsed = time.time() - start + report.add_step("PyInstaller 打包", False, elapsed, str(e)[:200]) + print(f" ❌ PyInstaller 打包 {elapsed:>7.2f}s {str(e)[:200]}") + else: + await run_step("Nuitka 编译", lambda: _stage_nuitka(config)[0]) + + # 阶段 5: 清理 + await run_step("清理临时文件", _stage_cleanup, mode) + + # 阶段 7: Vue 前端构建 + await run_step("Vue 前端构建", _stage_build_vue) + + # 总结 + report.end_time = time.time() + report.print_summary() + + if not report.all_success: + sys.exit(1) + + +def build(): + """构建入口""" + asyncio.run(main()) + + +if __name__ == "__main__": + build() + +``` + +`./src/chongming/app/__init__.py` +``` +""" +chongming FastAPI 应用入口 + +重构为配置驱动的微服务网关: +- WorkerManager: 统一管理 Zenoh worker 存活检测、负载均衡、超时重试、缓存 +- RouteGenerator: 根据配置自动生成 FastAPI 端点 +- 插件工厂函数动态加载 +""" +import os + +import time +import subprocess +import asyncio +from contextlib import asynccontextmanager +import boto3 +from botocore.exceptions import ClientError +from botocore.config import Config +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from fastapi.middleware.cors import CORSMiddleware + +from .core.config import get_config, get_workers_config +from .core.logger import get_logger +from .core.static_files import SVFSStaticFiles +from .routes import storage_router + +config = get_config() +logger = get_logger("app") + +try: + env = config["default"]["env"] +except KeyError: # pragma: no cover + raise ValueError("配置不存在") + + +@asynccontextmanager +async def lifespan(app: FastAPI): + import zenoh + from diskcache import Cache + from .core.worker_manager import WorkerManager + from .core.route_generator import register_dynamic_routes + + env = os.environ.copy() + env["NANIO_ACCESS_KEY"] = "minioadmin" + env["NANIO_SECRET_KEY"] = "minioadmin" + + nanio_process = subprocess.Popen( + ["nanio", "serve", "--data-dir", "./data/storage"], + env=env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + app.state.nanio_process = nanio_process + + await asyncio.sleep(1) # 等待 nanio 启动完成 + + s3_client_for_init = boto3.client( + "s3", + endpoint_url="http://127.0.0.1:9000", + aws_access_key_id="minioadmin", + aws_secret_access_key="minioadmin", + config=Config(signature_version="s3v4"), + region_name="us-east-1" + ) + try: + s3_client_for_init.head_bucket(Bucket="chongming") + except ClientError as e: + if e.response["Error"]["Code"] == "404": + s3_client_for_init.create_bucket(Bucket="chongming") + logger.info("Bucket 'chongming' 已创建") + else: + raise + app.state.s3_client = s3_client_for_init + app.state.storage_bucket = "chongming" + app.state.storage_presign_expire = 300 + app.state.storage_max_size = 100 * 1024 * 1024 # 100MB + + # 1. 建立 Zenoh 会话 + zconfig = zenoh.Config() + zconfig.from_file("config/zenoh_config.json5") + + try: + session = zenoh.open(zconfig) + logger.info("Zenoh 会话已建立 (TCP 自连接模式)") + except Exception as e: + logger.error(f"Zenoh 会话建立失败: {e}") + raise + + app.state.zenoh_session = session + logger.info("Zenoh 会话已建立") + + # 2. 创建 WorkerManager + cache = Cache(config["cache"]["cache_store_path"]) + worker_manager = WorkerManager(session, cache) + app.state.worker_manager = worker_manager + + # 3. 启动 Worker 存活刷新任务 + refresh_task = asyncio.create_task(worker_manager.refresh_workers()) + app.state.refresh_task = refresh_task + + # 4. 动态加载 worker 插件(根据配置) + workers_config_path = config.get("plugins_worker", {}).get("config_path", "config/workers/config.toml") + workers_conf = get_workers_config(workers_config_path).get("workers", []) + print(workers_conf) + worker_tasks = [] + for wc in workers_conf: + module_name = wc.get("module", "") + factory_name = wc.get("factory", "") + count = wc.get("count", 1) + worker_config = wc.get("config", {}) + + if not module_name or not factory_name: + logger.warning(f"Worker 配置不完整: {wc}") + continue + + try: + # 动态导入模块 + module = __import__(module_name, fromlist=[factory_name]) + factory = getattr(module, factory_name) + print(f"[factory]({factory_name}):{factory}") + # 调用工厂函数,传入 session、自定义配置、副本数和 worker_manager + # worker_manager 会传递给每个 worker,让 worker 启动后直接注册到本地管理器 + tasks = factory(session, worker_config, count, worker_manager=worker_manager) + worker_tasks.extend(tasks) + logger.info(f"已加载 Worker 插件: {module_name}.{factory_name} (副本数: {count})") + except Exception as e: + logger.error(f"加载 Worker 插件失败 {module_name}: {e}") + + app.state.worker_tasks = worker_tasks + logger.info(f"已启动 {len(worker_tasks)} 个 Worker") + + # 5. 注册动态路由 + register_dynamic_routes(app, worker_manager) + + # 短暂等待确保 worker 状态已同步 + await asyncio.sleep(0.5) + + # 初始同步完成后打印 worker 状态 + worker_status = { + wtype: len(workers) + for wtype, workers in app.state.worker_manager._workers_cache.items() + } + logger.info(f"Worker 就绪状态: {worker_status}") + + yield # 应用运行中... + + # 6. 清理 + logger.info("正在关闭应用...") + + # 停止 Worker 刷新任务 + refresh_task.cancel() + try: + await refresh_task + except asyncio.CancelledError: + pass + + # 取消所有 Worker 任务 + for task in worker_tasks: + task.cancel() + if worker_tasks: + await asyncio.gather(*worker_tasks, return_exceptions=True) + + # 关闭 Zenoh 会话 + session.close() + logger.info("应用已关闭") + + nanio_process.terminate() + nanio_process.wait() + logger.info("Nanio 进程已终止") + + +cors = config[env].get("cors", {}) +app = FastAPI( + title=config["default"]["app"]["name"], + version=config["default"]["app"]["version"], + description=config["default"]["app"]["description"], + docs_url="/docs" if config[env]["debug"] else None, + redoc_url="/redoc" if config[env]["debug"] else None, + openapi_url="/openapi.json" if config[env]["debug"] else None, + lifespan=lifespan, +) + +app.add_middleware(CORSMiddleware, **cors) + + +@app.middleware("http") +async def access_log_middleware(request: Request, call_next): + """ + HTTP访问日志中间件,记录每个请求的详细信息。 + + 记录的信息包括: + - 请求方法和路径 + - 响应状态码 + - 请求处理时长(毫秒) + - 客户端IP地址 + + 参数: + request: FastAPI请求对象 + call_next: 下一个中间件或路由处理函数 + + 返回: + Response: 处理后的响应对象 + """ + start = time.perf_counter() + response = await call_next(request) + duration = (time.perf_counter() - start) * 1000 + client = request.client.host if request.client else "-" + + logger.info( + f"{request.method} {request.url.path} status={response.status_code} " + f"duration={duration:.2f}ms client={client}", + extra={"status_code": response.status_code}, + ) + return response + + +@app.get("/", summary="根路径") +async def root(): + """ + 根路径端点,返回应用的基本信息。 + + 返回: + dict: 包含应用名称、版本、文档地址和健康检查地址的字典 + """ + return { + "message": f"欢迎使用 {config['default']['app']['name']}", + "version": config["default"]["app"]["version"], + "docs": "/docs" if config[env]["debug"] else None, + "health": f"{config['default']['prefix']}/health", + } + + +@app.exception_handler(Exception) +async def global_exception_handler(_: Request, exc: Exception): + """ + 全局异常处理器,捕获并处理应用中未捕获的异常。 + + 在调试模式下返回详细的异常信息,在生产模式下返回通用错误提示。 + + 参数: + _: FastAPI请求对象(未使用) + exc: 捕获到的异常对象 + + 返回: + JSONResponse: 包含错误信息的JSON响应,状态码为500 + """ + logger.error(f"全局异常: {exc}") + return JSONResponse( + status_code=500, + content={ + "error": "内部服务器错误", + "detail": str(exc) if config[env]["debug"] else "请查看服务器日志", + }, + ) + + +# 静态文件服务(可选) +if vfs_db_path := config[env].get("file_system", {}).get("path", None): + logger.info("静态文件已存在,将使用静态文件服务") + static_files_handler = SVFSStaticFiles(directory="/", vfs_db_path=vfs_db_path) + app.mount("/static", static_files_handler, name="static") + +app.include_router(storage_router) + +``` + +`./src/chongming/app/core/route_generator.py` +``` +""" +动态路由生成器 + +根据配置文件中的 worker 路由定义,动态生成 FastAPI 端点。 +为每个端点生成带有正确 Pydantic 类型注解的处理函数,确保 OpenAPI 文档 +正确反映参数类型、响应结构,并注册到 components/schemas。 +""" +import asyncio +import json +from typing import Any, Dict, List, Optional, get_type_hints +from inspect import Parameter, Signature + +from fastapi import FastAPI, HTTPException, APIRouter, Query, Path as FastAPIPath, Depends +from pydantic import create_model, Field, BaseModel + +from .worker_manager import WorkerManager +from .logger import get_logger + +logger = get_logger("route_generator") + +# 简单类型映射 +TYPE_MAP: Dict[str, Any] = { + "float": float, + "int": int, + "str": str, + "bool": bool, + "any": Any, +} + + +def _parse_python_type(type_str: str) -> Any: + """解析配置中的类型字符串为 Python 类型对象""" + type_str = type_str.strip() + if type_str.startswith("list[") or type_str.startswith("List["): + inner = type_str[5:-1].strip() + inner_type = _parse_python_type(inner) + return List[inner_type] # type: ignore + if type_str.startswith("dict[") or type_str.startswith("Dict["): + inner = type_str[5:-1].strip() + parts = [p.strip() for p in inner.split(",", 1)] + if len(parts) == 2: + key_type = _parse_python_type(parts[0]) + val_type = _parse_python_type(parts[1]) + return Dict[key_type, val_type] # type: ignore + return Dict[str, Any] # type: ignore + return TYPE_MAP.get(type_str, str) + + +def generate_request_model( + param_spec: Dict[str, str], model_name: str = "AutoRequest" +) -> type[BaseModel]: + """生成 Pydantic 请求体模型""" + fields: Dict[str, Any] = {} + for param_name, param_type_str in param_spec.items(): + py_type = _parse_python_type(param_type_str) + fields[param_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + + +def generate_response_model( + response_spec: Dict[str, Any], model_name: str = "AutoResponse" +) -> Optional[type[BaseModel]]: + """生成 Pydantic 响应模型""" + if not response_spec: + return None + fields: Dict[str, Any] = {} + for field_name, field_spec in response_spec.items(): + if isinstance(field_spec, dict): + py_type = _parse_python_type(field_spec.get("type", "str")) + required = field_spec.get("required", True) + default_val = field_spec.get("default", ...) + if required: + fields[field_name] = (py_type, Field(...)) + else: + fields[field_name] = (py_type, Field(default=default_val if default_val is not ... else None)) + else: + py_type = _parse_python_type(str(field_spec)) + fields[field_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + + +class RouteConfig: + """单个路由配置模型""" + + def __init__(self, data: Dict[str, Any]): + self.subject: str = data.get("subject", "") + self.path: str = data.get("path", "") + self.method: str = data.get("method", "GET").upper() + self.params: List[str] = data.get("params", []) + self.response_model: Dict[str, Any] = data.get("response_model", {}) + self.summary: str = data.get("summary", "") + self.docstring: str = data.get("docstring", "") + self.timeout: float = data.get("timeout", 3.0) + self.cacheable: bool = data.get("cacheable", False) + + def parse_params(self) -> Dict[str, str]: + """解析 params 列表为 {name: type} 字典""" + result: Dict[str, str] = {} + for param in self.params: + if ":" in param: + name, type_str = param.split(":", 1) + result[name.strip()] = type_str.strip() + else: + result[param.strip()] = "str" + return result + + def extract_path_params(self) -> List[str]: + """从路径中提取路径参数,如 /add/{a}/{b} -> ['a', 'b']""" + return [ + seg[1:-1] for seg in self.path.split("/") + if seg.startswith("{") and seg.endswith("}") + ] + + +# 默认的 worker 配置目录 +WORKER_CONFIG_DIR = "config/workers" + + +def load_worker_routes_from_dir(worker_type: str) -> List[Dict[str, Any]]: + """ + 从 config/workers/{worker_type}/ 目录加载路由配置。 + + 目录结构: + config/workers/{worker_type}/ + config.toml # [worker] 分组信息 + registration + defaults + add.toml # 接口名 = 文件名(不含扩展名) + [GET] + summary = ... + params = [...] + response_model = { ... } + [POST] + ... # 同一个接口支持多个请求方法 + + 返回: List[Dict], 每个 dict 包含 flatten 后的路由配置, + 每个方法节(如 GET) 生成一个独立的路由条目。 + """ + from pathlib import Path + import tomllib + + base_dir = Path(WORKER_CONFIG_DIR) / worker_type + if not base_dir.is_dir(): + logger.warning(f"Worker 配置目录不存在: {base_dir}") + return [] + + # 1. 读取分组配置 + group_cfg_path = base_dir / "config.toml" + group_defaults: Dict[str, Any] = {} + registration: Dict[str, Any] = {} + router_prefix: str = "" + if group_cfg_path.exists(): + with open(group_cfg_path, "rb") as f: + cfg = tomllib.load(f) + w = cfg.get("worker", {}) + group_defaults = w.get("defaults", {}) + registration = w.get("registration", {}) + router_prefix = registration.get("router_prefix", "") + + # 2. 遍历所有接口 toml 文件 + routes: List[Dict[str, Any]] = [] + for route_file in sorted(base_dir.glob("*.toml")): + if route_file.name == "config.toml": + continue + + # 文件名(不含 .toml)作为路径名 + route_name = route_file.stem # 例如 "add" + base_path = router_prefix + "/" + route_name # 例如 "/calc/add" + + with open(route_file, "rb") as f: + route_data = tomllib.load(f) + + # 每个方法节(GET/POST/PUT/DELETE)生成一个独立路由 + for method, spec in route_data.items(): + if not isinstance(spec, dict): + logger.warning(f"忽略非 dict 的方法节: {route_file.name}[{method}]") + continue + + # 合并: spec 显式字段优先,然后 group_defaults 兜底 + route = dict(spec) + for key, default_val in group_defaults.items(): + if key not in route: + route[key] = default_val + + # 覆盖关键字段 - method 始终从 TOML 节键获取,不被 defaults 覆盖 + route["method"] = method.upper() + route["path"] = route.get("path", base_path) + if "subject" not in route: + # 自动生成 subject: worker_type.route_name (如 "calc.add") + route["subject"] = f"{worker_type}.{route_name}" + + routes.append(route) + logger.debug( + f" 已加载: {route['method']} {route['path']} -> {route['subject']}" + ) + + logger.info(f"从 {base_dir} 加载了 {len(routes)} 个路由") + return routes + + +def register_dynamic_routes(app: FastAPI, worker_manager: WorkerManager): + """根据 config/workers/ 目录结构注册所有动态路由""" + from pathlib import Path + + workers_base = Path(WORKER_CONFIG_DIR) + if not workers_base.is_dir(): + logger.info(f"Worker 配置目录不存在: {WORKER_CONFIG_DIR}") + return + + router = APIRouter() + extra_schemas: Dict[str, type[BaseModel]] = {} + + # 扫描所有 worker 子目录(每个子目录名 = worker_type) + for worker_dir in sorted(workers_base.iterdir()): + if not worker_dir.is_dir(): + continue + worker_type = worker_dir.name + routes = load_worker_routes_from_dir(worker_type) + if not routes: + continue + + for route_conf in routes: + _register_single_route(router, worker_manager, worker_type, route_conf, extra_schemas) + + app.include_router(router) + + # 将额外 schema 注册到 OpenAPI 的 components/schemas + if extra_schemas: + _inject_schemas_to_openapi(app, extra_schemas) + + logger.info(f"动态路由注册完成,worker 配置目录: {WORKER_CONFIG_DIR}") + + +def _inject_schemas_to_openapi(app: FastAPI, schemas: Dict[str, type[BaseModel]]): + """ + 将自定义 Pydantic 模型注册到 FastAPI 的 OpenAPI components/schemas 中。 + + 通过重写 app.openapi() 方法注入,因为 FastAPI 在生命周期内只生成一次 + OpenAPI schema,之后会被缓存。 + """ + original_openapi = app.openapi + + def patched_openapi() -> dict[str, Any]: + if app.openapi_schema is not None: + return app.openapi_schema + schema = original_openapi() + if schema is None: + return {"openapi": "3.1.0", "info": {"title": "Chongming", "version": "1.0.0"}, "paths": {}} + # 注入我们的 schema + schemas_dict = schema.setdefault("components", {}).setdefault("schemas", {}) + for name, model in schemas.items(): + if name not in schemas_dict: + schemas_dict[name] = model.model_json_schema() + app.openapi_schema = schema + return schema + + app.openapi = patched_openapi + + +def _make_body_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str, RequestModel: type[BaseModel]): + """创建用于 Body 请求(POST/PUT/PATCH)的端点函数""" + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + async def endpoint( + body: RequestModel, # type: ignore + ) -> Any: + try: + payload = body.model_dump() + payload.setdefault("_method", http_method) + result = await worker_manager.call( + worker_type=worker_type, + subject=subject, + payload=payload, + timeout=timeout, + cacheable=cacheable, + ) + return result + except RuntimeError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + return endpoint + + +def _make_query_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str, path_params: List[str], query_params: Dict[str, str]): + """ + 创建用于 Query 请求(GET/DELETE)的端点函数。 + + 通过设置函数 __signature__ 和 __annotations__ 为每个参数赋予正确的 Pydantic 类型, + 使 FastAPI 能在 OpenAPI schema 中生成正确的 type / format 信息。 + 请求模型 schema 通过 extra_schemas 机制单独注入到 components/schemas。 + """ + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + # 合并路径参数和查询参数 + param_spec_combined: Dict[str, str] = {} + param_spec_combined.update({k: query_params.get(k, "str") for k in path_params if k in query_params}) + param_spec_combined.update(query_params) + + # 无参数情况 + if not param_spec_combined: + async def _simple_endpoint() -> Any: + try: + result = await worker_manager.call( + worker_type=worker_type, + subject=subject, + payload={"_method": http_method}, + timeout=timeout, + cacheable=cacheable, + ) + return result + except RuntimeError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + return _simple_endpoint + + # 构造参数名列表(用于从 kwargs 中提取) + param_names = list(param_spec_combined.keys()) + + async def endpoint(**kwargs: Any) -> Any: + payload = {k: kwargs[k] for k in param_names} + payload.setdefault("_method", http_method) + try: + result = await worker_manager.call( + worker_type=worker_type, + subject=subject, + payload=payload, + timeout=timeout, + cacheable=cacheable, + ) + return result + except RuntimeError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + # 构建带类型注解的 Parameter 列表 + params: List[Parameter] = [] + annotations: Dict[str, Any] = {} + for name, type_str in param_spec_combined.items(): + py_type = _parse_python_type(type_str) + annotations[name] = py_type + if name in path_params: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=FastAPIPath(...), annotation=py_type)) + else: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=Query(...), annotation=py_type)) + + # 设置 __annotations__(FastAPI 通过 get_type_hints 读取) + endpoint.__annotations__ = annotations + # 设置 __signature__(FastAPI 优先于此获取参数列表) + endpoint.__signature__ = Signature(params, return_annotation=Dict[str, Any]) # type: ignore + + return endpoint + + +def _register_single_route( + router: APIRouter, + worker_manager: WorkerManager, + worker_type: str, + route_conf: Dict[str, Any], + extra_schemas: Optional[Dict[str, type[BaseModel]]] = None, +): + """注册单个路由,生成正确的函数签名以便 OpenAPI 正确生成文档""" + rc = RouteConfig(route_conf) + param_spec = rc.parse_params() + path_params = rc.extract_path_params() + query_params = {k: v for k, v in param_spec.items() if k not in path_params} + + # 生成干净的模型名称(替换路径中的 / 和 {} 为 _) + # 模型名称包含 HTTP method 以区分同名路径的不同调用类型 + clean_path = rc.path.replace("/", "_").replace("{", "").replace("}", "").strip("_") + if not clean_path: + clean_path = "root" + method_tag = rc.method.upper() + response_model_name = f"{worker_type}_{clean_path}_{method_tag}_Response" + request_model_name = f"{worker_type}_{clean_path}_{method_tag}_Request" + + # 生成响应模型 + ResponseModel = generate_response_model( + rc.response_model, response_model_name + ) + + if rc.method in ("POST", "PUT", "PATCH"): + RequestModel = generate_request_model( + param_spec, request_model_name + ) + endpoint = _make_body_endpoint(worker_manager, rc, worker_type, RequestModel) + else: + # 对 GET 端点也生成 Request 模型并注册到 extra_schemas + if param_spec: + RequestModel = generate_request_model( + param_spec, request_model_name + ) + if extra_schemas is not None and request_model_name not in extra_schemas: + extra_schemas[request_model_name] = RequestModel + endpoint = _make_query_endpoint(worker_manager, rc, worker_type, path_params, query_params) + + # 注册路由 - 使用 worker_type 作为 OpenAPI 分组标签 + # 每个 worker 类型自动成为一个独立的分组,显示在 Swagger UI 中 + router.add_api_route( + path=rc.path, + endpoint=endpoint, + methods=[rc.method], + summary=rc.summary, + description=rc.docstring, + response_model=ResponseModel, + tags=[worker_type], + include_in_schema=True, + ) + logger.info(f"已注册动态路由: {rc.method} {rc.path} -> worker {worker_type}.{rc.subject} | tags=[{worker_type}]") + +``` + +`./src/chongming/app/core/worker_manager.py` +``` +import asyncio +import random +import json +import orjson +import time +from typing import Any, Dict, List, Optional, Set +import zenoh +from diskcache import Cache +from .logger import get_logger + +logger = get_logger("worker_manager") + +_SENTINEL = object() # 哨兵值,用于标记"No reply received" + + +class WorkerManager: + """ + 统一 Worker 管理器 + + 封装 Zenoh 会话,维护每个 worker 类型的存活节点列表, + 提供负载均衡、超时重试、缓存等能力的统一 RPC 调用入口。 + + 支持两种 worker 注册方式: + 1. 分布式:通过 Zenoh liveliness 自动发现 + 2. 本地:通过 register_worker() 直接注册(用于同进程 worker) + """ + + def __init__(self, session: zenoh.Session, cache: Optional[Cache] = None): + self.session = session + self.cache = cache + self._workers_cache: Dict[str, List[str]] = {} # type -> list of worker key_exprs + self._local_workers: Dict[str, List[str]] = {} # type -> list of locally registered workers + self._lock = asyncio.Lock() + self._refresh_interval = 15 # seconds + + def register_worker(self, worker_type: str, worker_id: str): + """ + 直接注册一个本地 worker(同进程内使用) + 这样无需等待 liveliness 发现,立即可用。 + + 同步执行(仅修改内存字典),确保调用后立即可被 get_worker 发现。 + """ + key_expr = f"worker/{worker_type}/{worker_id}" + # 同步直接写入 _local_workers,无需异步锁 + # 因为 _local_workers 和 _workers_cache 在调用 call() 时通过 await get_worker() 读取 + self._local_workers.setdefault(worker_type, []).append(key_expr) + logger.debug(f"本地 worker 已注册: {key_expr}") + + def unregister_worker(self, worker_type: str, worker_id: str): + """取消注册一个本地 worker""" + key_expr = f"worker/{worker_type}/{worker_id}" + if worker_type in self._local_workers: + try: + self._local_workers[worker_type].remove(key_expr) + except ValueError: + pass + + async def refresh_workers(self): + """定期刷新存活 worker 列表(合并 Zenoh liveliness + 本地注册)""" + while True: + try: + # 1. 从 Zenoh liveliness 获取分布式 workers + replies = self.session.liveliness().get("worker/*/*") + new_cache: Dict[str, List[str]] = {} + for reply in replies: + if reply.ok: + key = str(reply.ok.key_expr) # e.g. "worker/example/worker-0" + parts = key.split("/") + if len(parts) >= 3: + worker_type = parts[1] + new_cache.setdefault(worker_type, []).append(key) + + # 2. 合并本地注册的 workers + for wtype, workers in self._local_workers.items(): + existing = set(new_cache.get(wtype, [])) + for w in workers: + if w not in existing: + new_cache.setdefault(wtype, []).append(w) + + async with self._lock: + self._workers_cache = new_cache + logger.debug(f"Worker 存活列表更新: {self._workers_cache}") + except Exception as e: + logger.error(f"刷新 worker 列表失败: {e}") + await asyncio.sleep(self._refresh_interval) + + async def get_worker(self, worker_type: str) -> Optional[str]: + """获取一个可用的 worker 地址(简单随机负载均衡)""" + async with self._lock: + workers = self._workers_cache.get(worker_type, []) + # 如果缓存为空,回退到本地注册表(适用于启动初期 refresh_workers 尚未执行时) + if not workers: + local = self._local_workers.get(worker_type, []) + if local: + return random.choice(local) + return None + return random.choice(workers) + + def is_success_response(self, result: dict) -> bool: + """根据业务约定判断响应是否成功。 + 假设成功响应没有 'error' 字段,或 'code' 为 0。 + """ + if "error" in result and result["error"]: + return False + if "code" in result and result["code"] != 0: + return False + return True + + async def call( + self, + worker_type: str, + subject: str, + payload: Dict[str, Any], + timeout: float = 3.0, + retries: int = 1, + cacheable: bool = False, + ) -> Any: + """ + 统一调用入口:缓存 > 负载均衡 > 超时重试 + + Args: + worker_type: Worker 类型标识(如 "example") + subject: 调用的主题(如 "calc.add") + payload: 请求参数 + timeout: 单次调用超时(秒) + retries: 失败重试次数 + cacheable: 是否允许缓存结果(若为 False 则跳过缓存读写) + + Returns: + Dict[str, Any]: 调用结果 + + Raises: + RuntimeError: 无可用 worker 或所有重试均失败 + """ + # 1. 缓存检查(仅在 cacheable=True 时检查) + cache_key = f"{worker_type}:{subject}:{json.dumps(payload, sort_keys=True)}" + if cacheable and self.cache is not None: + if cache_key in self.cache: + logger.debug(f"命中缓存: {cache_key}") + return self.cache[cache_key] + + # 2. 获取可用 worker + worker_expr = await self.get_worker(worker_type) + if not worker_expr: + raise RuntimeError(f"No available worker for type {worker_type}") + + full_key = f"{worker_expr}/{subject}" + payload_bytes = orjson.dumps(payload) + + for attempt in range(retries + 1): + try: + reply_iter = self.session.get( + full_key, + payload=payload_bytes, + consolidation=zenoh.ConsolidationMode.NONE, + ) + # 使用辅助函数获取第一个回复,避免 StopIteration 在 asyncio.to_thread 中传播的问题 + def _get_first(iterator): + """线程安全地获取迭代器的第一个元素""" + for item in iterator: + return item + return _SENTINEL + + first_reply = await asyncio.wait_for( + asyncio.to_thread(_get_first, reply_iter), + timeout=timeout, + ) + if first_reply is _SENTINEL: + logger.warning( + f"Worker {worker_expr} 无回复,尝试重试 {attempt+1}/{retries+1}" + ) + continue + + reply = first_reply + if reply.ok: # type: ignore + # result_str = str(reply.ok.payload) + payload_bytes = bytes(reply.ok.payload) if reply.ok.payload else b"" # type: ignore + if not payload_bytes: + result = {} # 或根据业务返回默认值 + else: + result = orjson.loads(payload_bytes) + # 缓存结果(仅在 cacheable=True 时写入) + if cacheable and self.cache is not None and self.is_success_response(result): + self.cache[cache_key] = result + return result + else: + error_payload = reply.err.payload # type: ignore + error_msg = str(error_payload) if error_payload else "unknown error" + logger.warning(f"Worker 返回错误: {error_msg} (尝试 {attempt+1}/{retries+1})") + continue # 重试 + except asyncio.TimeoutError: + logger.warning( + f"Worker {worker_expr} 超时,尝试重试 {attempt+1}/{retries+1}" + ) + continue + except Exception as e: + logger.error(f"调用 worker 异常: {e}") + raise + + raise RuntimeError( + f"Failed to call worker {worker_type} after {retries} retries" + ) + +``` + +`./src/chongming/app/core/config.py` +``` +import sys +from pathlib import Path +from typing import TypedDict, Literal, Optional, cast, List + + +# 根据Python版本选择合适的TOML解析库和Required类型 +if sys.version_info >= (3, 11): + import tomllib + from typing import Required +else: + import tomli as tomllib + from typing_extensions import Required + +# 设置 env 为 development 或 production + + +class DefaultApp(TypedDict): + """应用默认配置结构""" + + name: str + version: str + description: str + debug: bool + + +class Default(TypedDict): + """系统默认配置结构,包含应用信息、环境、路径等基础配置""" + + app: DefaultApp + env: Literal["production", "development"] + prefix: str + upload_path: str + + +class SqliteConnectionArgs(TypedDict, total=False): + """SQLite数据库连接参数配置""" + + timeout: int + check_same_thread: bool + + +class DatabaseSqlite(TypedDict, total=False): + """SQLite数据库连接池及行为配置""" + + echo: bool + pool_size: int + max_overflow: int + pool_timeout: int + pool_recycle: int + pool_pre_ping: bool + connect_args: SqliteConnectionArgs + + +class Database(TypedDict, total=False): + """数据库配置结构,支持SQLite、MySQL、PostgreSQL三种数据库类型""" + + type: Required[Literal["sqlite", "mysql", "postgresql"]] + url: Required[str] + generate_schemas: bool + sqlite: DatabaseSqlite + mysql: dict + postgresql: dict + database_path: str + + +class Security(TypedDict): + """安全相关配置,包括JWT令牌和安全策略设置""" + + secret_key: str + algorithm: Literal["HS256"] + access_token_expire_minutes: int + refresh_token_expire_days: int + max_sessions_per_user: int + + +class Server(TypedDict, total=False): + """服务器运行配置,包含主机、端口、工作进程等设置""" + + host: Required[str] + port: Required[int] + reload: bool + workers: int + access_log: bool + + +class Logging(TypedDict): + """日志系统配置,定义日志级别、输出格式和文件管理策略""" + + level: str + file: str + format: str + max_size: int + backup_count: int + + +class Cors(TypedDict, total=False): + """跨域资源共享(CORS)配置""" + + allow_origins: List[str] + allow_credentials: bool + allow_methods: List[str] + allow_headers: List[str] + + +class ModuleSystem(TypedDict, total=False): + """模块系统配置,定义模块加载类型和路径""" + + type: str + path: List[str] + + +class FileSystem(TypedDict, total=False): + """文件系统配置,定义存储类型和路径""" + + type: str + path: str + + +class Env(TypedDict, total=False): + """环境特定配置结构,用于开发和生产环境的差异化配置""" + + debug: Required[bool] + server: Required[Server] + logging: Required[Logging] + cors: Cors + module_system: ModuleSystem + file_system: FileSystem + + +class Scheduler(TypedDict): + """任务调度器配置""" + + job_store_path: str + + +class Cache(TypedDict): + """缓存系统配置""" + + cache_store_path: str + + +class Config(TypedDict): + """完整配置结构,整合所有配置项,包含默认配置、数据库、调度器、缓存、安全和环境配置""" + + default: Default + database: Database + scheduler: Scheduler + cache: Cache + security: Security + development: Env + production: Env + + +def load_config(config_path: Path = Path("config.toml")) -> Config: + """ + 从TOML配置文件加载配置数据 + + Args: + config_path: 配置文件路径,默认为当前目录下的config.toml + + Returns: + Config: 解析后的配置字典对象 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + """ + if not config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {config_path}") + with open(config_path, "rb") as f: + return cast(Config, tomllib.load(f)) + + +# 全局配置缓存变量,避免重复加载配置文件 +_config: Optional[Config] = None + + +def get_config(config_path: str = "config.toml") -> Config: + """ + 获取应用程序配置(带缓存机制) + + 首次调用时加载配置文件并缓存,后续调用直接返回缓存的配置对象 + + Args: + config_path: 配置文件路径,默认为config.toml + + Returns: + Config: 配置字典对象 + """ + global _config + if _config is None: + _config = load_config(Path(config_path)) + return _config + +def get_workers_config(config_path: str = "config/workers/config.toml") -> dict: + """ + 获取Worker插件配置列表 + + Args: + config_path: Worker配置文件路径,默认为config/workers/config.toml + + Returns: + list: Worker配置字典列表 + """ + if not Path(config_path).exists(): + raise FileNotFoundError(f"Worker配置文件不存在: {config_path}") + with open(config_path, "rb") as f: + return cast(dict, tomllib.load(f)) + +``` + +`./src/chongming/app/core/__init__.py` +``` + +``` + +`./src/chongming/app/core/logger.py` +``` +import re +import logging +import datetime +from pathlib import Path +from logging.handlers import RotatingFileHandler + +from ..core.config import get_config + + +config = get_config() +env = config["default"]["env"] +server_config = config[env]["server"] +log_config = config[env]["logging"] + + +class ColoredFormatter(logging.Formatter): + """ + 为 HTTP 状态码添加颜色的日志格式化器 + + 根据 HTTP 状态码的首位数字为日志消息中的状态码添加不同颜色: + - 2xx: 绿色(成功) + - 3xx: 青色(重定向) + - 4xx: 黄色(客户端错误) + - 5xx: 红色(服务器错误) + + Attributes: + COLORS: 状态码首位数字到 ANSI 颜色代码的映射字典 + RESET: ANSI 重置颜色代码 + """ + + COLORS = { + "2": "\033[32m", # 绿色 2xx + "3": "\033[36m", # 青色 3xx + "4": "\033[33m", # 黄色 4xx + "5": "\033[31m", # 红色 5xx + } + RESET = "\033[0m" + + def format(self, record): + """ + 格式化日志记录,为状态码添加颜色 + + 如果日志记录包含 status_code 属性,则查找消息中的 "status=数字" 模式 + 并为其添加对应的颜色代码。格式化完成后恢复原始消息以避免影响其他 handler。 + + Args: + record: 日志记录对象,应包含 msg 属性和可选的 status_code 属性 + + Returns: + str: 格式化后的日志消息字符串,状态码部分可能包含 ANSI 颜色代码 + """ + # 保存原始消息 + original_msg = record.msg + # 如果日志记录包含 status_code 属性,则为其添加颜色 + status_code = getattr(record, "status_code", None) + if status_code and isinstance(status_code, int): + first_digit = str(status_code)[0] + color = self.COLORS.get(first_digit, "") + colored_status = f"{color}{status_code}{self.RESET}" + # 替换消息中的 "status=数字" 为带颜色的版本 + record.msg = re.sub( + rf"status={status_code}\b", f"status={colored_status}", original_msg + ) + result = super().format(record) + # 恢复原始消息,避免影响其他 handler + record.msg = original_msg + return result + + +class ExcludeWatchFilesFilter(logging.Filter): + """ + 日志过滤器,用于排除第三方库的日志输出 + + 过滤掉来自 watchfiles、aiosqlite、apscheduler、sqlalchemy 等库的日志记录, + 避免这些库的详细日志污染应用日志输出。 + """ + + def __init__(self): + """ + 初始化过滤器,设置需要过滤的日志名称前缀列表 + """ + super().__init__() + self._filter_starts_with_tulpe = ( + "watchfiles", + "aiosqlite", + "apscheduler", + "sqlalchemy", + "sqlitedict", + "sqlalchemy.engine.Engine", + ) + + def filter(self, record): + """ + 判断是否应该保留该日志记录 + + Args: + record: 日志记录对象,包含 name 属性表示日志来源 + + Returns: + bool: 如果日志名称不以过滤列表中的任何前缀开头则返回 True(保留), + 否则返回 False(过滤掉) + """ + return not record.name.startswith(self._filter_starts_with_tulpe) + + +_configured_loggers = set() + + +def setup_logging(logger: logging.Logger, log_name: str = "app"): + """ + 配置日志系统,包括文件和控制台输出 + + 为指定的 logger 配置以下功能: + 1. 设置日志级别和格式 + 2. 创建按日期命名的轮转文件处理器(支持文件大小限制和备份数量控制) + 3. 创建带颜色格式化的控制台处理器 + 4. 应用过滤器排除第三方库日志 + + 该函数具有防重复配置机制,同一个 log_name 只会配置一次。 + + Args: + logger: 需要配置的 logging.Logger 实例 + log_name: 日志名称标识,用于区分不同的日志配置和生成日志文件名,默认为 "app" + + Returns: + None + + Raises: + 无显式抛出异常,但可能因文件系统权限问题导致目录创建或文件写入失败 + """ + if log_name in _configured_loggers: + return + _configured_loggers.add(log_name) + log_level = getattr(logging, log_config["level"], logging.INFO) + log_format = log_config["format"] + # 创建文件夹 + log_file_path = Path(log_config["file"]) + if log_file_path != Path("."): + log_file_path.mkdir(parents=True, exist_ok=True) + + # 配置根日志记录器 + logger.setLevel(log_level) + + # 创建格式化器 + plain_formatter = logging.Formatter(log_format) + color_formatter = ColoredFormatter(log_format) + + # 创建轮转文件处理器 - 限制文件大小为 10MB,保留 5 个备份文件 + max_bytes = log_config.get("max_size", 10 * 1024 * 1024) # 默认 10MB + backup_count = log_config.get("backup_count", 5) # 默认保留 5 个备份 + + file_handler = RotatingFileHandler( + f"{log_config['file']}/{log_name}-{datetime.date.today()}.log", + encoding="utf-8", + maxBytes=max_bytes, + backupCount=backup_count, + ) + file_handler.setFormatter(plain_formatter) + file_handler.addFilter(ExcludeWatchFilesFilter()) + + # 创建控制台处理器并添加过滤器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(color_formatter) + console_handler.addFilter(ExcludeWatchFilesFilter()) + + # 添加处理器到根日志记录器 + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + print( + f"日志系统已配置 - 级别: {logging.getLevelName(log_level)}, 文件: {log_config['file']} " + f"(最大:{max_bytes / 1024 / 1024:.1f}MB, 备份:{backup_count})" + ) + + +def get_logger(name: str) -> logging.Logger: + """ + 获取配置好的日志记录器实例 + + 根据名称获取或创建 logger 实例,并自动调用 setup_logging 进行配置。 + 如果该名称的 logger 已经配置过,则直接返回已配置的实例。 + + Args: + name: 日志记录器名称,通常使用模块名如 __name__ + + Returns: + logging.Logger: 已配置完成的日志记录器实例,可直接用于记录日志 + """ + logger = logging.getLogger(name) + setup_logging(logger, name) + return logger + +``` + +`./src/chongming/app/core/static_files.py` +``` +from fastapi import HTTPException +from fastapi.responses import Response +from fastapi.staticfiles import StaticFiles +from sqlite_vfs.core import SQLiteVFS + + +class SVFSStaticFiles(StaticFiles): + """ + 基于 SQLite VFS 的静态文件服务类 + + 该类继承自 FastAPI 的 StaticFiles,使用 SQLite 虚拟文件系统来存储和提供静态文件。 + 支持单页应用(SPA)的路由回退机制,当请求的文件不存在时,会自动返回 index.html。 + """ + + def __init__(self, directory, vfs_db_path="static.svfs"): + """ + 初始化 SVFSStaticFiles 实例 + + 参数: + directory (str): 静态文件的根目录路径 + vfs_db_path (str): SQLite VFS 数据库文件路径,默认为 "static.svfs" + + 返回值: + 无 + """ + vfs_db_path = vfs_db_path + self._vfs = SQLiteVFS(vfs_db_path, compress=False) + super().__init__(directory=directory) + + async def get_response(self, path: str, scope): + """ + 根据请求路径获取对应的静态文件响应 + + 该方法处理静态文件的请求逻辑,包括: + 1. 路径标准化处理 + 2. 文件存在性检查 + 3. SPA 路由回退支持 + 4. 目录索引处理 + 5. MIME 类型自动识别 + + 参数: + path (str): 请求的文件路径 + scope: ASGI 作用域对象,包含请求上下文信息 + + 返回值: + Response: FastAPI 响应对象,包含文件内容和正确的 Content-Type + + 异常: + HTTPException: 当文件不存在且无法回退到 index.html 时,抛出 404 错误 + """ + # 统一路径格式:以 / 开头,并将 Windows 反斜杠转为正斜杠 + normalized_path = "/" + path.replace("\\", "/") + + # 获取文件信息 + file_info = self._vfs.get_file_info(normalized_path) + + # 处理文件不存在的情况,尝试 SPA 回退机制 + if file_info is None: + # SPA 回退:如果不是静态资源扩展名,尝试返回 index.html + if not any( + normalized_path.endswith(ext) + for ext in [ + ".js", + ".css", + ".png", + ".jpg", + ".ico", + ".svg", + ".webp", + ".json", + ] + ): + index_info = self._vfs.get_file_info("/index.html") + if index_info: + content = self._vfs.read_file("/index.html") + return Response(content=content, media_type="text/html") + raise HTTPException(status_code=404, detail="File not found") + + # 处理目录请求,返回目录下的 index.html + if file_info["is_directory"]: + index_info = self._vfs.get_file_info(f"{normalized_path}/index.html") + if index_info: + content = self._vfs.read_file(f"{normalized_path}/index.html") + return Response(content=content, media_type="text/html") + raise HTTPException(status_code=404, detail="Directory index not found") + + # 读取文件内容并确定 MIME 类型 + content = self._vfs.read_file(normalized_path) + content_type = file_info.get("content_type") or "application/octet-stream" + # 简单根据扩展名补充 MIME 类型(可选) + if normalized_path.endswith(".js"): + content_type = "application/javascript" + elif normalized_path.endswith(".css"): + content_type = "text/css" + elif normalized_path.endswith(".html"): + content_type = "text/html" + elif normalized_path.endswith(".ico"): + content_type = "image/x-icon" + + return Response(content=content, media_type=content_type) + +``` + +`./src/chongming/app/routes/storage.py` +``` +# src/chongming/app/routes/storage.py +import uuid +import urllib.parse +from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Request +from fastapi.responses import StreamingResponse +from botocore.exceptions import ClientError + +router = APIRouter(prefix="/api/v1/storage", tags=["Storage"]) + + +@router.post("/upload", summary="上传文件") +async def upload_file( + request: Request, + file: UploadFile = File(...), + worker_type: str = Form(...), +): + """后端代理上传,直接写入 nanio""" + s3_client = request.app.state.s3_client + bucket = request.app.state.storage_bucket + object_key = f"{worker_type}/{uuid.uuid4()}/{file.filename}" + + try: + s3_client.upload_fileobj( + file.file, + bucket, + object_key, + ExtraArgs={"ContentType": file.content_type or "application/octet-stream"} + ) + except ClientError as e: + raise HTTPException(status_code=500, detail=f"S3 上传失败: {str(e)}") + + return { + "file_key": object_key, + "bucket": bucket, + "filename": file.filename, + "content_type": file.content_type, + "size": file.size or 0, # 注意:UploadFile.size 可能为 None,需读取后计算 + } + + +@router.get("/download/{file_key:path}") +async def download_file(request: Request, file_key: str): + s3_client = request.app.state.s3_client + bucket = request.app.state.storage_bucket + + try: + response = s3_client.get_object(Bucket=bucket, Key=file_key) + content_type = response.get('ContentType', 'application/octet-stream') + raw_filename = file_key.split('/')[-1] + # 百分号编码,确保仅 ASCII 字符 + encoded = urllib.parse.quote(raw_filename, safe='') + # 仅使用 filename*(现代浏览器都支持) + content_disposition = f"attachment; filename*=UTF-8''{encoded}" + + return StreamingResponse( + response['Body'].iter_chunks(), + media_type=content_type, + headers={'Content-Disposition': content_disposition} + ) + except ClientError as e: + if e.response['Error']['Code'] == 'NoSuchKey': + raise HTTPException(404, "文件不存在") + raise HTTPException(500, f"下载失败: {e}") +``` + +`./src/chongming/app/routes/__init__.py` +``` +from .storage import router as storage_router + +__all__ = ["storage_router"] +``` + diff --git a/public/chongming.ico b/public/chongming.ico new file mode 100644 index 0000000..8e36272 Binary files /dev/null and b/public/chongming.ico differ diff --git a/public/config.toml b/public/config.toml new file mode 100644 index 0000000..1e63fc1 --- /dev/null +++ b/public/config.toml @@ -0,0 +1,80 @@ +[default] +app.debug = false +app.description = "基于 FastAPI 的 chongming 打包脚手架" +app.name = "chongming" +app.version = "0.1.0" +env = "production" +prefix = "/api/v1" +upload_path = "uploads" + +[database] +generate_schemas = true # 生产环境使用迁移工具 +type = "sqlite" +url = "sqlite+aiosqlite:///./data/database.db" + +[database.sqlite] +connect_args.check_same_thread = false +connect_args.timeout = 15 +echo = false +echo_pool = false +max_overflow = 10 +pool_pre_ping = true +pool_recycle = 300 +pool_size = 20 +pool_timeout = 30 + +[scheduler] +job_store_path = "scheduler_jobs.db" + +[cache] +cache_store_path = "./" + +[security] +access_token_expire_minutes = 30 +algorithm = "HS256" +max_sessions_per_user = 5 +refresh_token_expire_days = 7 +secret_key = "your-secret-key-change-in-production" + +[production] +debug = false +env = "production" + +[production.server] +access_log = false +host = "0.0.0.0" +port = 8000 +reload = false +timeout_keep_alive = 65 +workers = 4 + +[production.logging] +backup_count = 5 # 最多保存 10 个日志文件 +file = "logs" +format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +level = "INFO" +max_size = 10485760 # 10MB + +[production.cors] +allow_credentials = true +allow_headers = ["*"] +allow_methods = ["*"] +allow_origins = ["http://192.168.137.5:8000", "http://localhost:8000", "http://127.0.0.1:8000"] + +[production.module_system] +path = [ + "resources/app.mbank", + "resources/plugins.mbank", +] +type = "module-bank" + +[production.file_system] +path = "static.svfs" +type = "svfs" + +[[workers]] +type = "example" +module = "plugins.example" +factory = "create_workers" +count = 3 +config = { type = "example" } diff --git a/public/main.py b/public/main.py new file mode 100644 index 0000000..20a3346 --- /dev/null +++ b/public/main.py @@ -0,0 +1,100 @@ +# main.py - 修复 reload 问题 +#!/usr/bin/env python3 +""" +FastAPI chongming 示例 +主入口文件,适配 chongming 打包系统 +""" +import os +import sys +import platform +from utils.launch import launch +from multiprocessing import freeze_support +import server + +use_shell = platform.system() == "Windows" + + +def app_run(app_config: dict, default_config: dict): + """ + 应用运行函数 - 适配模块银行环境 + + Args: + app_config: 应用配置字典,包含服务器配置、环境设置等 + default_config: 默认配置字典,包含应用名称、版本等基础信息 + + Returns: + None: 此函数不返回值,直接启动服务器并阻塞运行 + """ + from app.core.logger import get_logger # type: ignore + + logger = get_logger("app") + + # 获取服务器配置 + server_config = app_config.get("server", {}) + host = server_config.get("host", "0.0.0.0") + port = server_config.get("port", 8000) + timeout_keep_alive = server_config.get("timeout_keep_alive", 5) + + # 打包环境强制禁用重载 + is_frozen = getattr(sys, "frozen", False) + reload = False if is_frozen else server_config.get("reload", False) + # 支持通过环境变量 APP_WORKERS 覆盖 worker 数量(方便嵌入式设备调优) + default_workers = server_config.get("workers", 4) + workers = int(os.environ.get("APP_WORKERS", str(default_workers))) + + # 显示启动信息 + env = app_config.get("env", "development") + logger.info("=" * 50) + logger.info(f"🚀 启动 {default_config.get('app.name', 'FastAPI 应用')}") + logger.info(f"📦 版本: {default_config.get('app.version', '1.0.0')}") + logger.info(f"🌍 环境: {env}") + logger.info(f"📍 地址: http://{host}:{port}") + logger.info(f"📚 文档: http://{host}:{port}/docs") + logger.info(f"🔧 调试: {'启用' if app_config.get('debug', False) else '禁用'}") + logger.info(f"🔄 重载: {'启用' if reload else '禁用'}") + logger.info(f"👥 Workers: {workers}") + logger.info("=" * 50) + + # 根据操作系统选择不同的服务器启动方式 + if use_shell: + import uvicorn + + uvicorn.run( + "server:app", + host=host, + port=port, + reload=reload, + workers=workers, + log_level="debug" if app_config.get("debug", False) else "info", + access_log=False, + timeout_keep_alive=timeout_keep_alive, + ) + else: + from gunicorn.app.wsgiapp import run + + sys.argv = [ + "gunicorn", + "server:app", + "-k", + "uvicorn.workers.UvicornWorker", + "-w", + str(workers), + "-b", + f"{host}:{port}", + ] + run() + + +def main(): + """ + 主函数入口 - 初始化多进程支持并启动应用 + + Returns: + None: 此函数不返回值,调用launch函数启动应用 + """ + freeze_support() + launch(app_run, "production") + + +if __name__ == "__main__": + main() diff --git a/public/server.py b/public/server.py new file mode 100644 index 0000000..6e586ec --- /dev/null +++ b/public/server.py @@ -0,0 +1,21 @@ +# server.py +import sys +from utils.launch import init_module_bank +from utils.config import ConfigLoader + +# 加载配置文件并初始化模块银行 +config = ConfigLoader("config.toml") +init_module_bank(config) + +# 从数据库动态导入 app 模块 +# 该模块存储在 SQLite 数据库中,通过自定义导入机制加载 +try: + import app # type: ignore + + app_instance = app.app +except ImportError as e: + print(f"无法从数据库导入 app 模块: {e}") + sys.exit(1) + +# 暴露 ASGI 应用实例,供 uvicorn 服务器使用 +app = app_instance diff --git a/public/utils/__init__.py b/public/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/public/utils/config.py b/public/utils/config.py new file mode 100644 index 0000000..bef1306 --- /dev/null +++ b/public/utils/config.py @@ -0,0 +1,184 @@ +# config_loader.py - 读取config.toml的示例代码 + + +import os +import sys +from pathlib import Path +from typing import Any, Dict, Optional + +# 根据Python版本选择合适的TOML解析库 +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib + + +class ConfigLoader: + """TOML配置文件加载器 + + 提供配置文件的加载、访问和管理功能。支持自动查找配置文件、 + 点号分隔的嵌套键访问、配置节获取以及配置重载等功能。 + + Attributes: + config_path: 配置文件的路径 + _config: 存储解析后的配置数据 + """ + + def __init__(self, config_path: Optional[str] = None): + """ + 初始化配置加载器 + + Args: + config_path: 配置文件路径,如果为None则自动查找 + + Raises: + FileNotFoundError: 当无法找到配置文件时抛出异常 + """ + if config_path is None: + # 自动查找配置文件 + config_path = self._find_config_file() + + self.config_path = Path(config_path) + self._config: Dict[str, Any] = {} + self.load() + + def _find_config_file(self) -> str: + """ + 自动查找配置文件 + + 按照预定义的优先级顺序搜索配置文件: + 1. 当前目录下的config.toml + 2. config子目录下的config.toml + 3. 用户配置目录~/.config/myapp/config.toml + 4. 系统配置目录/etc/myapp/config.toml + + Returns: + str: 找到的第一个配置文件路径 + + Raises: + FileNotFoundError: 当所有可能的位置都未找到配置文件时抛出异常 + """ + possible_paths = [ + "config.toml", + "config/config.toml", + "~/.config/myapp/config.toml", + "/etc/myapp/config.toml", + ] + + for path in possible_paths: + expanded_path = os.path.expanduser(path) + if os.path.exists(expanded_path): + return expanded_path + + raise FileNotFoundError("未找到配置文件") + + def load(self) -> None: + """ + 加载配置文件 + + 读取并解析TOML格式的配置文件,将配置数据存储到内部字典中。 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + tomllib.TOMLDecodeError: 当配置文件格式不正确时抛出异常 + """ + if not self.config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {self.config_path}") + + with open(self.config_path, "r", encoding="utf-8") as f: + self._config = tomllib.loads(f.read()) + + def get(self, key: str, default: Any = None) -> Any: + """ + 获取配置值,支持点号分隔的路径 + + 通过点号分隔的键路径访问嵌套的配置值。例如: + - "database.host" 访问 {"database": {"host": "localhost"}} + - "server.port" 访问 {"server": {"port": 8080}} + + Args: + key: 配置键,支持点号分隔的嵌套路径 + default: 当键不存在时返回的默认值,默认为None + + Returns: + Any: 配置值,如果键不存在则返回默认值 + """ + keys = key.split(".") + value = self._config + + try: + for k in keys: + value = value[k] + return value + except (KeyError, TypeError): + return default + + def get_section(self, section: str) -> Dict[str, Any]: + """ + 获取整个配置节 + + 返回指定配置节的完整字典内容,适用于需要访问某个配置节下 + 所有配置项的场景。 + + Args: + section: 配置节名称 + + Returns: + Dict[str, Any]: 配置节的内容字典,如果节不存在则返回空字典 + """ + return self._config.get(section, {}) + + def reload(self) -> None: + """ + 重新加载配置文件 + + 从磁盘重新读取配置文件并更新内部配置数据。 + 适用于配置文件在运行时被修改的场景。 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + """ + self.load() + + def __getitem__(self, key: str) -> Any: + """ + 支持字典式访问 + + 允许使用方括号语法访问配置值,如:config["database.host"] + + Args: + key: 配置键 + + Returns: + Any: 配置值 + + Raises: + KeyError: 当键不存在且无默认值时抛出异常(通过get方法处理) + """ + return self.get(key) + + def __contains__(self, key: str) -> bool: + """ + 检查配置是否存在 + + 支持使用in操作符检查配置键是否存在,如:"database.host" in config + + Args: + key: 要检查的配置键 + + Returns: + bool: 如果配置键存在且值不为None则返回True,否则返回False + """ + return self.get(key) is not None + + @property + def all(self) -> Dict[str, Any]: + """ + 获取所有配置 + + 返回完整的配置字典,适用于需要遍历或导出所有配置的场景。 + + Returns: + Dict[str, Any]: 包含所有配置的字典 + """ + return self._config diff --git a/public/utils/launch.py b/public/utils/launch.py new file mode 100644 index 0000000..1084601 --- /dev/null +++ b/public/utils/launch.py @@ -0,0 +1,62 @@ +from .config import ConfigLoader +import os +from module_bank import PythonToSQLite +from typing import Callable + + + +def init_module_bank(config): + """ + 初始化模块银行系统,将指定路径下的Python模块打包并安装导入器。 + + 该函数会验证所有模块路径的有效性,创建PythonToSQLite打包器实例, + 列出所有可用模块,并为每个打包器安装导入器以支持模块的动态加载。 + + Args: + config (dict): 配置字典,必须包含 'production' -> 'module_system' -> 'path' 键, + 其值为模块路径列表 + + Raises: + ValueError: 当配置的模块路径中有任何一个不存在时抛出异常 + """ + MODULE_BANK_PATH_LIST = config["production"]["module_system"]["path"] + for module_path in MODULE_BANK_PATH_LIST: + if not os.path.exists(module_path): + raise ValueError(f"模块路径不存在: {module_path}") + + # 为每个模块路径创建PythonToSQLite打包器实例 + packer_list = [ + PythonToSQLite(module_path) for module_path in MODULE_BANK_PATH_LIST + ] + # 遍历所有打包器,列出模块信息并安装导入器 + for packer in packer_list: + key = packer.get_key() + modules = packer.list_modules() + for module in modules: + print(f"{module['module_name']} {'[包]' if module['is_package'] else ''}") + packer.install_importer(key=key) + + +def launch(run_fun: Callable[[dict, dict], None], run_mode: str): + """ + 启动应用程序,加载配置并执行指定的运行函数。 + + 该函数从config.toml文件中加载配置,根据运行模式获取对应的应用配置和默认配置, + 然后调用传入的运行函数执行应用程序逻辑。 + + Args: + run_fun (Callable[[dict, dict], None]): 运行函数,接收两个参数: + - app_config (dict): 当前运行模式的配置 + - default_config (dict): 默认配置 + run_mode (str): 运行模式,用于从配置文件中获取对应模式的配置项 + + Returns: + None + """ + config = ConfigLoader("config.toml") + + # 获取对应运行模式的配置 + default_config = config.get("default", {}) + app_config = config.get(run_mode, {}) + + run_fun(app_config, default_config) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9b3cdfd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,59 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +dependencies = [ + "apscheduler", + "boto3>=1.43.48", + "chongming-request", + "chongming-worker", + "diskcache", + "eclipse-zenoh", + "fastapi[uvicorn]", + "gunicorn", + "modelvault>=0.16.0", + "module-bank", + "nanio>=0.1.5", + "orjson>=3.11.9", + "python-multipart>=0.0.32", + "sqlite-vfs", + "tomli", + "typing-extensions", + "uvicorn", +] +description = "基于 modules-bank 的 fastapi 打包脚手架" +name = "chongming-zenoh" +readme = "README.md" +requires-python = ">=3.12" +version = "0.1.0" + +[dependency-groups] +dev = [ + "nuitka", + "pyarmor", + "pyinstaller", + "ruff", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming"] + +[[tool.uv.index]] +url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" + +[tool.uv.workspace] +members = [ + "utils/worker", + "utils/request", +] + +[tool.uv.sources] +chongming-worker = { workspace = true } +chongming-request = { workspace = true } + +[project.scripts] +build = "chongming.scripts.build:build" +gen_models = "chongming.scripts.gen_models:gen_models" +gunicorn = "chongming:gunicorn_serve" +serve = "chongming:serve" diff --git a/src/chongming-web/ .env.development b/src/chongming-web/ .env.development new file mode 100644 index 0000000..8dea7a9 --- /dev/null +++ b/src/chongming-web/ .env.development @@ -0,0 +1 @@ +VITE_API_BASE_URL = "http://localhost:8000" \ No newline at end of file diff --git a/src/chongming-web/.env.production b/src/chongming-web/.env.production new file mode 100644 index 0000000..0bc1ba6 --- /dev/null +++ b/src/chongming-web/.env.production @@ -0,0 +1 @@ +VITE_API_BASE_URL = "/" \ No newline at end of file diff --git a/src/chongming-web/.gitignore b/src/chongming-web/.gitignore new file mode 100644 index 0000000..cd68f14 --- /dev/null +++ b/src/chongming-web/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs diff --git a/src/chongming-web/.oxfmtrc.json b/src/chongming-web/.oxfmtrc.json new file mode 100644 index 0000000..4bddb67 --- /dev/null +++ b/src/chongming-web/.oxfmtrc.json @@ -0,0 +1,5 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "semi": false, + "singleQuote": true +} diff --git a/src/chongming-web/.vscode/extensions.json b/src/chongming-web/.vscode/extensions.json new file mode 100644 index 0000000..81ba7c1 --- /dev/null +++ b/src/chongming-web/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "Vue.volar", + "oxc.oxc-vscode" + ] +} diff --git a/src/chongming-web/README.md b/src/chongming-web/README.md new file mode 100644 index 0000000..ea6a924 --- /dev/null +++ b/src/chongming-web/README.md @@ -0,0 +1,42 @@ +# chongming-web + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` diff --git a/src/chongming-web/env.d.ts b/src/chongming-web/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/chongming-web/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/chongming-web/index.html b/src/chongming-web/index.html new file mode 100644 index 0000000..9e5fc8f --- /dev/null +++ b/src/chongming-web/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/src/chongming-web/package-lock.json b/src/chongming-web/package-lock.json new file mode 100644 index 0000000..601cdc5 --- /dev/null +++ b/src/chongming-web/package-lock.json @@ -0,0 +1,3726 @@ +{ + "name": "chongming-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chongming-web", + "version": "0.0.0", + "dependencies": { + "pinia": "^3.0.4", + "vue": "^3.5.38", + "vue-router": "^5.1.0" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.13.2", + "@vitejs/plugin-vue": "^6.0.7", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "oxfmt": "^0.54.0", + "typescript": "~6.0.0", + "vite": "^8.0.16", + "vite-plugin-vue-devtools": "^8.1.2", + "vue-tsc": "^3.3.5" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.54.0.tgz", + "integrity": "sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.54.0.tgz", + "integrity": "sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.54.0.tgz", + "integrity": "sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.54.0.tgz", + "integrity": "sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.54.0.tgz", + "integrity": "sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.54.0.tgz", + "integrity": "sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.54.0.tgz", + "integrity": "sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.54.0.tgz", + "integrity": "sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.54.0.tgz", + "integrity": "sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.54.0.tgz", + "integrity": "sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.54.0.tgz", + "integrity": "sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.54.0.tgz", + "integrity": "sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.54.0.tgz", + "integrity": "sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.54.0.tgz", + "integrity": "sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.54.0.tgz", + "integrity": "sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.54.0.tgz", + "integrity": "sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.54.0.tgz", + "integrity": "sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.54.0.tgz", + "integrity": "sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.54.0.tgz", + "integrity": "sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node24": { + "version": "24.0.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node24/-/node24-24.0.4.tgz", + "integrity": "sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmmirror.com/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.7", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", + "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vitejs/plugin-vue-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue-jsx/-/plugin-vue-jsx-5.1.6.tgz", + "integrity": "sha512-YXvi4as2clxt6DFw5+a0tTA97ntiQXm/raR8ofNj3aNwwdlVGTiG2gp7EvfZW17P50acL/9bP0ccF4XnqNmlgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-syntax-typescript": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7", + "@rolldown/pluginutils": "^1.0.1", + "@vue/babel-plugin-jsx": "^2.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.0.0" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@vue-macros/common/-/common-3.1.2.tgz", + "integrity": "sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-2.0.1.tgz", + "integrity": "sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-2.0.1.tgz", + "integrity": "sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@vue/babel-helper-vue-transform-on": "2.0.1", + "@vue/babel-plugin-resolve-type": "2.0.1", + "@vue/shared": "^3.5.22" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-2.0.1.tgz", + "integrity": "sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.4", + "@vue/compiler-sfc": "^3.5.22" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.10", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz", + "integrity": "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.10" + } + }, + "node_modules/@vue/devtools-core": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-core/-/devtools-core-8.1.5.tgz", + "integrity": "sha512-5e5jQOEssCdZA1wlFEUkIDtb+cAOWuLNWJ52fm4PBWbF7e3oTnM2fneaL42E5lJoolAaUQ678tv/XEb3h4e86Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.5", + "@vue/devtools-shared": "^8.1.5" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-core/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.10", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz", + "integrity": "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.10", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.10", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz", + "integrity": "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.3.7.tgz", + "integrity": "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmmirror.com/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.42", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/browserslist": { + "version": "4.28.5", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.5.tgz", + "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001800", + "electron-to-chromium": "^1.5.387", + "node-releases": "^2.0.50", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001803", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", + "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.389", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", + "dev": true, + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-6.0.0.tgz", + "integrity": "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.51", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", + "integrity": "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-run-all2": { + "version": "9.0.2", + "resolved": "https://registry.npmmirror.com/npm-run-all2/-/npm-run-all2-9.0.2.tgz", + "integrity": "sha512-+dd4SO2jAlLE06OzmJKzIe6QvvjXezcbmobnh8usR0a8BzQCABTdqTXqVPji0ICOhSQpIIrkGd7IzNl5iDaRSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^1.0.0", + "read-package-json-fast": "^6.0.0", + "shell-quote": "^1.8.4", + "which": "^7.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0", + "npm": ">= 10" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmmirror.com/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oxfmt": { + "version": "0.54.0", + "resolved": "https://registry.npmmirror.com/oxfmt/-/oxfmt-0.54.0.tgz", + "integrity": "sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinypool": "2.1.0" + }, + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxfmt/binding-android-arm-eabi": "0.54.0", + "@oxfmt/binding-android-arm64": "0.54.0", + "@oxfmt/binding-darwin-arm64": "0.54.0", + "@oxfmt/binding-darwin-x64": "0.54.0", + "@oxfmt/binding-freebsd-x64": "0.54.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.54.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.54.0", + "@oxfmt/binding-linux-arm64-gnu": "0.54.0", + "@oxfmt/binding-linux-arm64-musl": "0.54.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.54.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.54.0", + "@oxfmt/binding-linux-riscv64-musl": "0.54.0", + "@oxfmt/binding-linux-s390x-gnu": "0.54.0", + "@oxfmt/binding-linux-x64-gnu": "0.54.0", + "@oxfmt/binding-linux-x64-musl": "0.54.0", + "@oxfmt/binding-openharmony-arm64": "0.54.0", + "@oxfmt/binding-win32-arm64-msvc": "0.54.0", + "@oxfmt/binding-win32-ia32-msvc": "0.54.0", + "@oxfmt/binding-win32-x64-msvc": "0.54.0" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-1.0.0.tgz", + "integrity": "sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/read-package-json-fast/-/read-package-json-fast-6.0.0.tgz", + "integrity": "sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^6.0.0", + "npm-normalize-package-bin": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/tinypool/-/tinypool-2.1.0.tgz", + "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "8.1.4", + "resolved": "https://registry.npmmirror.com/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", + "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^4.0.0", + "vite-hot-client": "^2.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" + } + }, + "node_modules/vite-dev-rpc/node_modules/birpc": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vite-hot-client": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/vite-hot-client/-/vite-hot-client-2.2.0.tgz", + "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.4.1", + "resolved": "https://registry.npmmirror.com/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", + "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.0", + "error-stack-parser-es": "^1.0.5", + "obug": "^2.1.1", + "ohash": "^2.0.11", + "open": "^11.0.0", + "perfect-debounce": "^2.1.0", + "sirv": "^3.0.2", + "unplugin-utils": "^0.3.1", + "vite-dev-rpc": "^2.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.1.5.tgz", + "integrity": "sha512-QSVntSN0sbVV08CZntMWOfF8McyPyYyYd19sChLjxYyFCC7Fcpey74ztw1uCCU23wTmR2yWPb92uaxVD4Lw0Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.1.5", + "@vue/devtools-kit": "^8.1.5", + "@vue/devtools-shared": "^8.1.5", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^6.0.0" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-devtools/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-6.0.0.tgz", + "integrity": "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/vite-plugin-vue-inspector/node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-inspector/node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-inspector/node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-5.1.0.tgz", + "integrity": "sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^8.0.0-rc.4", + "@vue-macros/common": "^3.1.1", + "@vue/devtools-api": "^8.1.2", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "scule": "^1.3.0", + "tinyglobby": "^0.2.16", + "unplugin": "^3.0.0", + "unplugin-utils": "^0.3.1", + "yaml": "^2.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34", + "pinia": "^3.0.4", + "vite": "^7.0.0 || ^8.0.0", + "vue": "^3.5.34" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vue-router/node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.2.tgz", + "integrity": "sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/parser": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-8.0.0.tgz", + "integrity": "sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@babel/types": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-8.0.0.tgz", + "integrity": "sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-8.1.5.tgz", + "integrity": "sha512-YJipMVAKe5wT5CWf5kTYCaNV7NMNjFVxJkIkJaJ4W/nCxEBzlZzrOsYKeCymdCrFZmBS/+wTWFoUs3Jf/Q6XSQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.5" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "license": "MIT" + }, + "node_modules/vue-router/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.7", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.3.7.tgz", + "integrity": "sha512-+C+rgD49wAQ5bUTl2sp5a8Bzg4YoldMNXM+g7CFe604MYcQ8PrZPMQhIjJSzKXtPBCa+C5ayMipqjbA7splekQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.7" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/which/-/which-7.0.0.tgz", + "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/src/chongming-web/package.json b/src/chongming-web/package.json new file mode 100644 index 0000000..185b5e8 --- /dev/null +++ b/src/chongming-web/package.json @@ -0,0 +1,35 @@ +{ + "name": "chongming-web", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build", + "format": "oxfmt src/" + }, + "dependencies": { + "pinia": "^3.0.4", + "vue": "^3.5.38", + "vue-router": "^5.1.0" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.13.2", + "@vitejs/plugin-vue": "^6.0.7", + "@vitejs/plugin-vue-jsx": "^5.1.5", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "oxfmt": "^0.54.0", + "typescript": "~6.0.0", + "vite": "^8.0.16", + "vite-plugin-vue-devtools": "^8.1.2", + "vue-tsc": "^3.3.5" + }, + "engines": { + "node": "^22.18.0 || >=24.12.0" + } +} diff --git a/src/chongming-web/public/favicon.ico b/src/chongming-web/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/src/chongming-web/public/favicon.ico differ diff --git a/src/chongming-web/src/App.vue b/src/chongming-web/src/App.vue new file mode 100644 index 0000000..7905b05 --- /dev/null +++ b/src/chongming-web/src/App.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/chongming-web/src/assets/base.css b/src/chongming-web/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/src/chongming-web/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/src/chongming-web/src/assets/logo.svg b/src/chongming-web/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/src/chongming-web/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/src/chongming-web/src/assets/main.css b/src/chongming-web/src/assets/main.css new file mode 100644 index 0000000..36fb845 --- /dev/null +++ b/src/chongming-web/src/assets/main.css @@ -0,0 +1,35 @@ +@import './base.css'; + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + font-weight: normal; +} + +a, +.green { + text-decoration: none; + color: hsla(160, 100%, 37%, 1); + transition: 0.4s; + padding: 3px; +} + +@media (hover: hover) { + a:hover { + background-color: hsla(160, 100%, 37%, 0.2); + } +} + +@media (min-width: 1024px) { + body { + display: flex; + place-items: center; + } + + #app { + display: grid; + grid-template-columns: 1fr 1fr; + padding: 0 2rem; + } +} diff --git a/src/chongming-web/src/components/HelloWorld.vue b/src/chongming-web/src/components/HelloWorld.vue new file mode 100644 index 0000000..d174cf8 --- /dev/null +++ b/src/chongming-web/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/chongming-web/src/components/TheWelcome.vue b/src/chongming-web/src/components/TheWelcome.vue new file mode 100644 index 0000000..8b731d9 --- /dev/null +++ b/src/chongming-web/src/components/TheWelcome.vue @@ -0,0 +1,95 @@ + + + diff --git a/src/chongming-web/src/components/WelcomeItem.vue b/src/chongming-web/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/src/chongming-web/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/src/chongming-web/src/components/icons/IconCommunity.vue b/src/chongming-web/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/src/chongming-web/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/src/chongming-web/src/components/icons/IconDocumentation.vue b/src/chongming-web/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/src/chongming-web/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/src/chongming-web/src/components/icons/IconEcosystem.vue b/src/chongming-web/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/src/chongming-web/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/src/chongming-web/src/components/icons/IconSupport.vue b/src/chongming-web/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/src/chongming-web/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/src/chongming-web/src/components/icons/IconTooling.vue b/src/chongming-web/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/src/chongming-web/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/src/chongming-web/src/main.ts b/src/chongming-web/src/main.ts new file mode 100644 index 0000000..5dcad83 --- /dev/null +++ b/src/chongming-web/src/main.ts @@ -0,0 +1,14 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/src/chongming-web/src/router/index.ts b/src/chongming-web/src/router/index.ts new file mode 100644 index 0000000..3e49915 --- /dev/null +++ b/src/chongming-web/src/router/index.ts @@ -0,0 +1,23 @@ +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../views/HomeView.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView, + }, + { + path: '/about', + name: 'about', + // route level code-splitting + // this generates a separate chunk (About.[hash].js) for this route + // which is lazy-loaded when the route is visited. + component: () => import('../views/AboutView.vue'), + }, + ], +}) + +export default router diff --git a/src/chongming-web/src/stores/counter.ts b/src/chongming-web/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/src/chongming-web/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/src/chongming-web/src/views/AboutView.vue b/src/chongming-web/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/src/chongming-web/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/chongming-web/src/views/HomeView.vue b/src/chongming-web/src/views/HomeView.vue new file mode 100644 index 0000000..d5c0217 --- /dev/null +++ b/src/chongming-web/src/views/HomeView.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/chongming-web/tsconfig.app.json b/src/chongming-web/tsconfig.app.json new file mode 100644 index 0000000..c0f2d86 --- /dev/null +++ b/src/chongming-web/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + // Extra safety for array and object lookups, but may have false positives. + "noUncheckedIndexedAccess": true, + + // Path mapping for cleaner imports. + "paths": { + "@/*": ["./src/*"] + }, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/src/chongming-web/tsconfig.json b/src/chongming-web/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/src/chongming-web/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/src/chongming-web/tsconfig.node.json b/src/chongming-web/tsconfig.node.json new file mode 100644 index 0000000..c9b2bad --- /dev/null +++ b/src/chongming-web/tsconfig.node.json @@ -0,0 +1,27 @@ +// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping. +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + // Most tools use transpilation instead of Node.js's native type-stripping. + // Bundler mode provides a smoother developer experience. + "module": "preserve", + "moduleResolution": "bundler", + + // Include Node.js types and avoid accidentally including other `@types/*` packages. + "types": ["node"], + + // Disable emitting output during `vue-tsc --build`, which is used for type-checking only. + "noEmit": true, + + // `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking. + // Specified here to keep it out of the root directory. + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/src/chongming-web/vite.config.ts b/src/chongming-web/vite.config.ts new file mode 100644 index 0000000..ace127e --- /dev/null +++ b/src/chongming-web/vite.config.ts @@ -0,0 +1,20 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueJsx(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +}) diff --git a/src/chongming/__init__.py b/src/chongming/__init__.py new file mode 100644 index 0000000..37f052d --- /dev/null +++ b/src/chongming/__init__.py @@ -0,0 +1,60 @@ +import uvicorn +from .app.core.config import load_config +from .app.core.logger import get_logger + + +def serve(): + """ + 使用 uvicorn 启动服务 + + 从配置文件中加载服务器配置和日志配置,然后使用 uvicorn 启动应用服务。 + 该函数适用于开发环境或单进程部署场景。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + log_config = config[env]["logging"] + logger = get_logger("app") + logger.info(f"启动服务: {server_config}") + logger.info(f"启动日志: {log_config}") + uvicorn.run("chongming.app:app", **server_config) + + +def gunicorn_serve(): + """ + 使用 gunicorn 启动服务 + + 通过 gunicorn 作为进程管理器启动应用,使用 UvicornWorker 作为 worker 类型, + 支持多进程部署,适用于生产环境。 + + 从配置文件中读取服务器配置,动态设置 gunicorn 的 worker 数量、绑定地址和端口等参数。 + + Returns: + None: 该函数不返回值,会阻塞运行直到服务停止 + """ + import sys + from gunicorn.app.wsgiapp import run + + config = load_config() + env = config["default"]["env"] + server_config = config[env]["server"] + + # 构建 gunicorn 命令行参数并启动服务 + sys.argv = [ + "gunicorn", + "chongming.app:app", + "-k", + "uvicorn.workers.UvicornWorker", + "-w", + f"{server_config.get('workers', 4)}", + "-b", + f"{server_config.get('host', '0.0.0.0')}:{server_config.get('port', 8000)}", + ] + run() + + +if __name__ == "__main__": + serve() diff --git a/src/chongming/app/__init__.py b/src/chongming/app/__init__.py new file mode 100644 index 0000000..0c33926 --- /dev/null +++ b/src/chongming/app/__init__.py @@ -0,0 +1,263 @@ +""" +chongming FastAPI 应用入口 + +重构为配置驱动的微服务网关: +- WorkerManager: 统一管理 Zenoh worker 存活检测、负载均衡、超时重试、缓存 +- RouteGenerator: 根据配置自动生成 FastAPI 端点 +- 插件工厂函数动态加载 +""" +import os + +import time +import subprocess +import asyncio +from contextlib import asynccontextmanager +import boto3 +from botocore.exceptions import ClientError +from botocore.config import Config +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from fastapi.middleware.cors import CORSMiddleware + +from .core.config import get_config, get_workers_config +from .core.logger import get_logger +from .core.static_files import SVFSStaticFiles +from .routes import storage_router + +config = get_config() +logger = get_logger("app") + +try: + env = config["default"]["env"] +except KeyError: # pragma: no cover + raise ValueError("配置不存在") + + +@asynccontextmanager +async def lifespan(app: FastAPI): + import zenoh + from diskcache import Cache + from .core.worker_manager import WorkerManager + from .core.route_generator import register_dynamic_routes + + env = os.environ.copy() + env["NANIO_ACCESS_KEY"] = "minioadmin" + env["NANIO_SECRET_KEY"] = "minioadmin" + + nanio_process = subprocess.Popen( + ["nanio", "serve", "--data-dir", "./data/storage"], + env=env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + app.state.nanio_process = nanio_process + + await asyncio.sleep(1) # 等待 nanio 启动完成 + + s3_client_for_init = boto3.client( + "s3", + endpoint_url="http://127.0.0.1:9000", + aws_access_key_id="minioadmin", + aws_secret_access_key="minioadmin", + config=Config(signature_version="s3v4"), + region_name="us-east-1" + ) + try: + s3_client_for_init.head_bucket(Bucket="chongming") + except ClientError as e: + if e.response["Error"]["Code"] == "404": + s3_client_for_init.create_bucket(Bucket="chongming") + logger.info("Bucket 'chongming' 已创建") + else: + raise + app.state.s3_client = s3_client_for_init + app.state.storage_bucket = "chongming" + app.state.storage_presign_expire = 300 + app.state.storage_max_size = 100 * 1024 * 1024 # 100MB + + # 1. 建立 Zenoh 会话 + zconfig = zenoh.Config() + zconfig.from_file("config/zenoh_config.json5") + + try: + session = zenoh.open(zconfig) + logger.info("Zenoh 会话已建立 (TCP 自连接模式)") + except Exception as e: + logger.error(f"Zenoh 会话建立失败: {e}") + raise + + app.state.zenoh_session = session + logger.info("Zenoh 会话已建立") + + # 2. 创建 WorkerManager + cache = Cache(config["cache"]["cache_store_path"]) + worker_manager = WorkerManager(session, cache) + app.state.worker_manager = worker_manager + + # 3. 启动 Worker 存活刷新任务 + refresh_task = asyncio.create_task(worker_manager.refresh_workers()) + app.state.refresh_task = refresh_task + + # 4. 动态加载 worker 插件(根据配置) + workers_config_path = config.get("plugins_worker", {}).get("config_path", "config/workers/config.toml") + workers_conf = get_workers_config(workers_config_path).get("workers", []) + print(workers_conf) + worker_tasks = [] + for wc in workers_conf: + module_name = wc.get("module", "") + factory_name = wc.get("factory", "") + count = wc.get("count", 1) + worker_config = wc.get("config", {}) + + if not module_name or not factory_name: + logger.warning(f"Worker 配置不完整: {wc}") + continue + + try: + # 动态导入模块 + module = __import__(module_name, fromlist=[factory_name]) + factory = getattr(module, factory_name) + print(f"[factory]({factory_name}):{factory}") + # 调用工厂函数,传入 session、自定义配置、副本数和 worker_manager + # worker_manager 会传递给每个 worker,让 worker 启动后直接注册到本地管理器 + tasks = factory(session, worker_config, count, worker_manager=worker_manager) + worker_tasks.extend(tasks) + logger.info(f"已加载 Worker 插件: {module_name}.{factory_name} (副本数: {count})") + except Exception as e: + logger.error(f"加载 Worker 插件失败 {module_name}: {e}") + + app.state.worker_tasks = worker_tasks + logger.info(f"已启动 {len(worker_tasks)} 个 Worker") + + # 5. 注册动态路由 + register_dynamic_routes(app, worker_manager) + + # 短暂等待确保 worker 状态已同步 + await asyncio.sleep(0.5) + + # 初始同步完成后打印 worker 状态 + worker_status = { + wtype: len(workers) + for wtype, workers in app.state.worker_manager._workers_cache.items() + } + logger.info(f"Worker 就绪状态: {worker_status}") + + yield # 应用运行中... + + # 6. 清理 + logger.info("正在关闭应用...") + + # 停止 Worker 刷新任务 + refresh_task.cancel() + try: + await refresh_task + except asyncio.CancelledError: + pass + + # 取消所有 Worker 任务 + for task in worker_tasks: + task.cancel() + if worker_tasks: + await asyncio.gather(*worker_tasks, return_exceptions=True) + + # 关闭 Zenoh 会话 + session.close() + logger.info("应用已关闭") + + nanio_process.terminate() + nanio_process.wait() + logger.info("Nanio 进程已终止") + + +cors = config[env].get("cors", {}) +app = FastAPI( + title=config["default"]["app"]["name"], + version=config["default"]["app"]["version"], + description=config["default"]["app"]["description"], + docs_url="/docs" if config[env]["debug"] else None, + redoc_url="/redoc" if config[env]["debug"] else None, + openapi_url="/openapi.json" if config[env]["debug"] else None, + lifespan=lifespan, +) + +app.add_middleware(CORSMiddleware, **cors) + + +@app.middleware("http") +async def access_log_middleware(request: Request, call_next): + """ + HTTP访问日志中间件,记录每个请求的详细信息。 + + 记录的信息包括: + - 请求方法和路径 + - 响应状态码 + - 请求处理时长(毫秒) + - 客户端IP地址 + + 参数: + request: FastAPI请求对象 + call_next: 下一个中间件或路由处理函数 + + 返回: + Response: 处理后的响应对象 + """ + start = time.perf_counter() + response = await call_next(request) + duration = (time.perf_counter() - start) * 1000 + client = request.client.host if request.client else "-" + + logger.info( + f"{request.method} {request.url.path} status={response.status_code} " + f"duration={duration:.2f}ms client={client}", + extra={"status_code": response.status_code}, + ) + return response + + +@app.get("/", summary="根路径") +async def root(): + """ + 根路径端点,返回应用的基本信息。 + + 返回: + dict: 包含应用名称、版本、文档地址和健康检查地址的字典 + """ + return { + "message": f"欢迎使用 {config['default']['app']['name']}", + "version": config["default"]["app"]["version"], + "docs": "/docs" if config[env]["debug"] else None, + "health": f"{config['default']['prefix']}/health", + } + + +@app.exception_handler(Exception) +async def global_exception_handler(_: Request, exc: Exception): + """ + 全局异常处理器,捕获并处理应用中未捕获的异常。 + + 在调试模式下返回详细的异常信息,在生产模式下返回通用错误提示。 + + 参数: + _: FastAPI请求对象(未使用) + exc: 捕获到的异常对象 + + 返回: + JSONResponse: 包含错误信息的JSON响应,状态码为500 + """ + logger.error(f"全局异常: {exc}") + return JSONResponse( + status_code=500, + content={ + "error": "内部服务器错误", + "detail": str(exc) if config[env]["debug"] else "请查看服务器日志", + }, + ) + + +# 静态文件服务(可选) +if vfs_db_path := config[env].get("file_system", {}).get("path", None): + logger.info("静态文件已存在,将使用静态文件服务") + static_files_handler = SVFSStaticFiles(directory="/", vfs_db_path=vfs_db_path) + app.mount("/static", static_files_handler, name="static") + +app.include_router(storage_router) diff --git a/src/chongming/app/core/__init__.py b/src/chongming/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chongming/app/core/config.py b/src/chongming/app/core/config.py new file mode 100644 index 0000000..94eeee7 --- /dev/null +++ b/src/chongming/app/core/config.py @@ -0,0 +1,207 @@ +import sys +from pathlib import Path +from typing import TypedDict, Literal, Optional, cast, List + + +# 根据Python版本选择合适的TOML解析库和Required类型 +if sys.version_info >= (3, 11): + import tomllib + from typing import Required +else: + import tomli as tomllib + from typing_extensions import Required + +# 设置 env 为 development 或 production + + +class DefaultApp(TypedDict): + """应用默认配置结构""" + + name: str + version: str + description: str + debug: bool + + +class Default(TypedDict): + """系统默认配置结构,包含应用信息、环境、路径等基础配置""" + + app: DefaultApp + env: Literal["production", "development"] + prefix: str + upload_path: str + + +class SqliteConnectionArgs(TypedDict, total=False): + """SQLite数据库连接参数配置""" + + timeout: int + check_same_thread: bool + + +class DatabaseSqlite(TypedDict, total=False): + """SQLite数据库连接池及行为配置""" + + echo: bool + pool_size: int + max_overflow: int + pool_timeout: int + pool_recycle: int + pool_pre_ping: bool + connect_args: SqliteConnectionArgs + + +class Database(TypedDict, total=False): + """数据库配置结构,支持SQLite、MySQL、PostgreSQL三种数据库类型""" + + type: Required[Literal["sqlite", "mysql", "postgresql"]] + url: Required[str] + generate_schemas: bool + sqlite: DatabaseSqlite + mysql: dict + postgresql: dict + database_path: str + + +class Security(TypedDict): + """安全相关配置,包括JWT令牌和安全策略设置""" + + secret_key: str + algorithm: Literal["HS256"] + access_token_expire_minutes: int + refresh_token_expire_days: int + max_sessions_per_user: int + + +class Server(TypedDict, total=False): + """服务器运行配置,包含主机、端口、工作进程等设置""" + + host: Required[str] + port: Required[int] + reload: bool + workers: int + access_log: bool + + +class Logging(TypedDict): + """日志系统配置,定义日志级别、输出格式和文件管理策略""" + + level: str + file: str + format: str + max_size: int + backup_count: int + + +class Cors(TypedDict, total=False): + """跨域资源共享(CORS)配置""" + + allow_origins: List[str] + allow_credentials: bool + allow_methods: List[str] + allow_headers: List[str] + + +class ModuleSystem(TypedDict, total=False): + """模块系统配置,定义模块加载类型和路径""" + + type: str + path: List[str] + + +class FileSystem(TypedDict, total=False): + """文件系统配置,定义存储类型和路径""" + + type: str + path: str + + +class Env(TypedDict, total=False): + """环境特定配置结构,用于开发和生产环境的差异化配置""" + + debug: Required[bool] + server: Required[Server] + logging: Required[Logging] + cors: Cors + module_system: ModuleSystem + file_system: FileSystem + + +class Scheduler(TypedDict): + """任务调度器配置""" + + job_store_path: str + + +class Cache(TypedDict): + """缓存系统配置""" + + cache_store_path: str + + +class Config(TypedDict): + """完整配置结构,整合所有配置项,包含默认配置、数据库、调度器、缓存、安全和环境配置""" + + default: Default + database: Database + scheduler: Scheduler + cache: Cache + security: Security + development: Env + production: Env + + +def load_config(config_path: Path = Path("config.toml")) -> Config: + """ + 从TOML配置文件加载配置数据 + + Args: + config_path: 配置文件路径,默认为当前目录下的config.toml + + Returns: + Config: 解析后的配置字典对象 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + """ + if not config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {config_path}") + with open(config_path, "rb") as f: + return cast(Config, tomllib.load(f)) + + +# 全局配置缓存变量,避免重复加载配置文件 +_config: Optional[Config] = None + + +def get_config(config_path: str = "config.toml") -> Config: + """ + 获取应用程序配置(带缓存机制) + + 首次调用时加载配置文件并缓存,后续调用直接返回缓存的配置对象 + + Args: + config_path: 配置文件路径,默认为config.toml + + Returns: + Config: 配置字典对象 + """ + global _config + if _config is None: + _config = load_config(Path(config_path)) + return _config + +def get_workers_config(config_path: str = "config/workers/config.toml") -> dict: + """ + 获取Worker插件配置列表 + + Args: + config_path: Worker配置文件路径,默认为config/workers/config.toml + + Returns: + list: Worker配置字典列表 + """ + if not Path(config_path).exists(): + raise FileNotFoundError(f"Worker配置文件不存在: {config_path}") + with open(config_path, "rb") as f: + return cast(dict, tomllib.load(f)) diff --git a/src/chongming/app/core/logger.py b/src/chongming/app/core/logger.py new file mode 100644 index 0000000..2472958 --- /dev/null +++ b/src/chongming/app/core/logger.py @@ -0,0 +1,191 @@ +import re +import logging +import datetime +from pathlib import Path +from logging.handlers import RotatingFileHandler + +from ..core.config import get_config + + +config = get_config() +env = config["default"]["env"] +server_config = config[env]["server"] +log_config = config[env]["logging"] + + +class ColoredFormatter(logging.Formatter): + """ + 为 HTTP 状态码添加颜色的日志格式化器 + + 根据 HTTP 状态码的首位数字为日志消息中的状态码添加不同颜色: + - 2xx: 绿色(成功) + - 3xx: 青色(重定向) + - 4xx: 黄色(客户端错误) + - 5xx: 红色(服务器错误) + + Attributes: + COLORS: 状态码首位数字到 ANSI 颜色代码的映射字典 + RESET: ANSI 重置颜色代码 + """ + + COLORS = { + "2": "\033[32m", # 绿色 2xx + "3": "\033[36m", # 青色 3xx + "4": "\033[33m", # 黄色 4xx + "5": "\033[31m", # 红色 5xx + } + RESET = "\033[0m" + + def format(self, record): + """ + 格式化日志记录,为状态码添加颜色 + + 如果日志记录包含 status_code 属性,则查找消息中的 "status=数字" 模式 + 并为其添加对应的颜色代码。格式化完成后恢复原始消息以避免影响其他 handler。 + + Args: + record: 日志记录对象,应包含 msg 属性和可选的 status_code 属性 + + Returns: + str: 格式化后的日志消息字符串,状态码部分可能包含 ANSI 颜色代码 + """ + # 保存原始消息 + original_msg = record.msg + # 如果日志记录包含 status_code 属性,则为其添加颜色 + status_code = getattr(record, "status_code", None) + if status_code and isinstance(status_code, int): + first_digit = str(status_code)[0] + color = self.COLORS.get(first_digit, "") + colored_status = f"{color}{status_code}{self.RESET}" + # 替换消息中的 "status=数字" 为带颜色的版本 + record.msg = re.sub( + rf"status={status_code}\b", f"status={colored_status}", original_msg + ) + result = super().format(record) + # 恢复原始消息,避免影响其他 handler + record.msg = original_msg + return result + + +class ExcludeWatchFilesFilter(logging.Filter): + """ + 日志过滤器,用于排除第三方库的日志输出 + + 过滤掉来自 watchfiles、aiosqlite、apscheduler、sqlalchemy 等库的日志记录, + 避免这些库的详细日志污染应用日志输出。 + """ + + def __init__(self): + """ + 初始化过滤器,设置需要过滤的日志名称前缀列表 + """ + super().__init__() + self._filter_starts_with_tulpe = ( + "watchfiles", + "aiosqlite", + "apscheduler", + "sqlalchemy", + "sqlitedict", + "sqlalchemy.engine.Engine", + ) + + def filter(self, record): + """ + 判断是否应该保留该日志记录 + + Args: + record: 日志记录对象,包含 name 属性表示日志来源 + + Returns: + bool: 如果日志名称不以过滤列表中的任何前缀开头则返回 True(保留), + 否则返回 False(过滤掉) + """ + return not record.name.startswith(self._filter_starts_with_tulpe) + + +_configured_loggers = set() + + +def setup_logging(logger: logging.Logger, log_name: str = "app"): + """ + 配置日志系统,包括文件和控制台输出 + + 为指定的 logger 配置以下功能: + 1. 设置日志级别和格式 + 2. 创建按日期命名的轮转文件处理器(支持文件大小限制和备份数量控制) + 3. 创建带颜色格式化的控制台处理器 + 4. 应用过滤器排除第三方库日志 + + 该函数具有防重复配置机制,同一个 log_name 只会配置一次。 + + Args: + logger: 需要配置的 logging.Logger 实例 + log_name: 日志名称标识,用于区分不同的日志配置和生成日志文件名,默认为 "app" + + Returns: + None + + Raises: + 无显式抛出异常,但可能因文件系统权限问题导致目录创建或文件写入失败 + """ + if log_name in _configured_loggers: + return + _configured_loggers.add(log_name) + log_level = getattr(logging, log_config["level"], logging.INFO) + log_format = log_config["format"] + # 创建文件夹 + log_file_path = Path(log_config["file"]) + if log_file_path != Path("."): + log_file_path.mkdir(parents=True, exist_ok=True) + + # 配置根日志记录器 + logger.setLevel(log_level) + + # 创建格式化器 + plain_formatter = logging.Formatter(log_format) + color_formatter = ColoredFormatter(log_format) + + # 创建轮转文件处理器 - 限制文件大小为 10MB,保留 5 个备份文件 + max_bytes = log_config.get("max_size", 10 * 1024 * 1024) # 默认 10MB + backup_count = log_config.get("backup_count", 5) # 默认保留 5 个备份 + + file_handler = RotatingFileHandler( + f"{log_config['file']}/{log_name}-{datetime.date.today()}.log", + encoding="utf-8", + maxBytes=max_bytes, + backupCount=backup_count, + ) + file_handler.setFormatter(plain_formatter) + file_handler.addFilter(ExcludeWatchFilesFilter()) + + # 创建控制台处理器并添加过滤器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(color_formatter) + console_handler.addFilter(ExcludeWatchFilesFilter()) + + # 添加处理器到根日志记录器 + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + print( + f"日志系统已配置 - 级别: {logging.getLevelName(log_level)}, 文件: {log_config['file']} " + f"(最大:{max_bytes / 1024 / 1024:.1f}MB, 备份:{backup_count})" + ) + + +def get_logger(name: str) -> logging.Logger: + """ + 获取配置好的日志记录器实例 + + 根据名称获取或创建 logger 实例,并自动调用 setup_logging 进行配置。 + 如果该名称的 logger 已经配置过,则直接返回已配置的实例。 + + Args: + name: 日志记录器名称,通常使用模块名如 __name__ + + Returns: + logging.Logger: 已配置完成的日志记录器实例,可直接用于记录日志 + """ + logger = logging.getLogger(name) + setup_logging(logger, name) + return logger diff --git a/src/chongming/app/core/route_generator.py b/src/chongming/app/core/route_generator.py new file mode 100644 index 0000000..165b2f8 --- /dev/null +++ b/src/chongming/app/core/route_generator.py @@ -0,0 +1,421 @@ +""" +动态路由生成器 + +根据配置文件中的 worker 路由定义,动态生成 FastAPI 端点。 +为每个端点生成带有正确 Pydantic 类型注解的处理函数,确保 OpenAPI 文档 +正确反映参数类型、响应结构,并注册到 components/schemas。 +""" +import asyncio +import json +from typing import Any, Dict, List, Optional, get_type_hints +from inspect import Parameter, Signature + +from fastapi import FastAPI, HTTPException, APIRouter, Query, Path as FastAPIPath, Depends +from pydantic import create_model, Field, BaseModel + +from .worker_manager import WorkerManager +from .logger import get_logger + +logger = get_logger("route_generator") + +# 简单类型映射 +TYPE_MAP: Dict[str, Any] = { + "float": float, + "int": int, + "str": str, + "bool": bool, + "any": Any, +} + + +def _parse_python_type(type_str: str) -> Any: + """解析配置中的类型字符串为 Python 类型对象""" + type_str = type_str.strip() + if type_str.startswith("list[") or type_str.startswith("List["): + inner = type_str[5:-1].strip() + inner_type = _parse_python_type(inner) + return List[inner_type] # type: ignore + if type_str.startswith("dict[") or type_str.startswith("Dict["): + inner = type_str[5:-1].strip() + parts = [p.strip() for p in inner.split(",", 1)] + if len(parts) == 2: + key_type = _parse_python_type(parts[0]) + val_type = _parse_python_type(parts[1]) + return Dict[key_type, val_type] # type: ignore + return Dict[str, Any] # type: ignore + return TYPE_MAP.get(type_str, str) + + +def generate_request_model( + param_spec: Dict[str, str], model_name: str = "AutoRequest" +) -> type[BaseModel]: + """生成 Pydantic 请求体模型""" + fields: Dict[str, Any] = {} + for param_name, param_type_str in param_spec.items(): + py_type = _parse_python_type(param_type_str) + fields[param_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + + +def generate_response_model( + response_spec: Dict[str, Any], model_name: str = "AutoResponse" +) -> Optional[type[BaseModel]]: + """生成 Pydantic 响应模型""" + if not response_spec: + return None + fields: Dict[str, Any] = {} + for field_name, field_spec in response_spec.items(): + if isinstance(field_spec, dict): + py_type = _parse_python_type(field_spec.get("type", "str")) + required = field_spec.get("required", True) + default_val = field_spec.get("default", ...) + if required: + fields[field_name] = (py_type, Field(...)) + else: + fields[field_name] = (py_type, Field(default=default_val if default_val is not ... else None)) + else: + py_type = _parse_python_type(str(field_spec)) + fields[field_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + + +class RouteConfig: + """单个路由配置模型""" + + def __init__(self, data: Dict[str, Any]): + self.subject: str = data.get("subject", "") + self.path: str = data.get("path", "") + self.method: str = data.get("method", "GET").upper() + self.params: List[str] = data.get("params", []) + self.response_model: Dict[str, Any] = data.get("response_model", {}) + self.summary: str = data.get("summary", "") + self.docstring: str = data.get("docstring", "") + self.timeout: float = data.get("timeout", 3.0) + self.cacheable: bool = data.get("cacheable", False) + + def parse_params(self) -> Dict[str, str]: + """解析 params 列表为 {name: type} 字典""" + result: Dict[str, str] = {} + for param in self.params: + if ":" in param: + name, type_str = param.split(":", 1) + result[name.strip()] = type_str.strip() + else: + result[param.strip()] = "str" + return result + + def extract_path_params(self) -> List[str]: + """从路径中提取路径参数,如 /add/{a}/{b} -> ['a', 'b']""" + return [ + seg[1:-1] for seg in self.path.split("/") + if seg.startswith("{") and seg.endswith("}") + ] + + +# 默认的 worker 配置目录 +WORKER_CONFIG_DIR = "config/workers" + + +def load_worker_routes_from_dir(worker_type: str) -> List[Dict[str, Any]]: + """ + 从 config/workers/{worker_type}/ 目录加载路由配置。 + + 目录结构: + config/workers/{worker_type}/ + config.toml # [worker] 分组信息 + registration + defaults + add.toml # 接口名 = 文件名(不含扩展名) + [GET] + summary = ... + params = [...] + response_model = { ... } + [POST] + ... # 同一个接口支持多个请求方法 + + 返回: List[Dict], 每个 dict 包含 flatten 后的路由配置, + 每个方法节(如 GET) 生成一个独立的路由条目。 + """ + from pathlib import Path + import tomllib + + base_dir = Path(WORKER_CONFIG_DIR) / worker_type + if not base_dir.is_dir(): + logger.warning(f"Worker 配置目录不存在: {base_dir}") + return [] + + # 1. 读取分组配置 + group_cfg_path = base_dir / "config.toml" + group_defaults: Dict[str, Any] = {} + registration: Dict[str, Any] = {} + router_prefix: str = "" + if group_cfg_path.exists(): + with open(group_cfg_path, "rb") as f: + cfg = tomllib.load(f) + w = cfg.get("worker", {}) + group_defaults = w.get("defaults", {}) + registration = w.get("registration", {}) + router_prefix = registration.get("router_prefix", "") + + # 2. 遍历所有接口 toml 文件 + routes: List[Dict[str, Any]] = [] + for route_file in sorted(base_dir.glob("*.toml")): + if route_file.name == "config.toml": + continue + + # 文件名(不含 .toml)作为路径名 + route_name = route_file.stem # 例如 "add" + base_path = router_prefix + "/" + route_name # 例如 "/calc/add" + + with open(route_file, "rb") as f: + route_data = tomllib.load(f) + + # 每个方法节(GET/POST/PUT/DELETE)生成一个独立路由 + for method, spec in route_data.items(): + if not isinstance(spec, dict): + logger.warning(f"忽略非 dict 的方法节: {route_file.name}[{method}]") + continue + + # 合并: spec 显式字段优先,然后 group_defaults 兜底 + route = dict(spec) + for key, default_val in group_defaults.items(): + if key not in route: + route[key] = default_val + + # 覆盖关键字段 - method 始终从 TOML 节键获取,不被 defaults 覆盖 + route["method"] = method.upper() + route["path"] = route.get("path", base_path) + if "subject" not in route: + # 自动生成 subject: worker_type.route_name (如 "calc.add") + route["subject"] = f"{worker_type}.{route_name}" + + routes.append(route) + logger.debug( + f" 已加载: {route['method']} {route['path']} -> {route['subject']}" + ) + + logger.info(f"从 {base_dir} 加载了 {len(routes)} 个路由") + return routes + + +def register_dynamic_routes(app: FastAPI, worker_manager: WorkerManager): + """根据 config/workers/ 目录结构注册所有动态路由""" + from pathlib import Path + + workers_base = Path(WORKER_CONFIG_DIR) + if not workers_base.is_dir(): + logger.info(f"Worker 配置目录不存在: {WORKER_CONFIG_DIR}") + return + + router = APIRouter() + extra_schemas: Dict[str, type[BaseModel]] = {} + + # 扫描所有 worker 子目录(每个子目录名 = worker_type) + for worker_dir in sorted(workers_base.iterdir()): + if not worker_dir.is_dir(): + continue + worker_type = worker_dir.name + routes = load_worker_routes_from_dir(worker_type) + if not routes: + continue + + for route_conf in routes: + _register_single_route(router, worker_manager, worker_type, route_conf, extra_schemas) + + app.include_router(router) + + # 将额外 schema 注册到 OpenAPI 的 components/schemas + if extra_schemas: + _inject_schemas_to_openapi(app, extra_schemas) + + logger.info(f"动态路由注册完成,worker 配置目录: {WORKER_CONFIG_DIR}") + + +def _inject_schemas_to_openapi(app: FastAPI, schemas: Dict[str, type[BaseModel]]): + """ + 将自定义 Pydantic 模型注册到 FastAPI 的 OpenAPI components/schemas 中。 + + 通过重写 app.openapi() 方法注入,因为 FastAPI 在生命周期内只生成一次 + OpenAPI schema,之后会被缓存。 + """ + original_openapi = app.openapi + + def patched_openapi() -> dict[str, Any]: + if app.openapi_schema is not None: + return app.openapi_schema + schema = original_openapi() + if schema is None: + return {"openapi": "3.1.0", "info": {"title": "Chongming", "version": "1.0.0"}, "paths": {}} + # 注入我们的 schema + schemas_dict = schema.setdefault("components", {}).setdefault("schemas", {}) + for name, model in schemas.items(): + if name not in schemas_dict: + schemas_dict[name] = model.model_json_schema() + app.openapi_schema = schema + return schema + + app.openapi = patched_openapi + + +def _make_body_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str, RequestModel: type[BaseModel]): + """创建用于 Body 请求(POST/PUT/PATCH)的端点函数""" + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + async def endpoint( + body: RequestModel, # type: ignore + ) -> Any: + try: + payload = body.model_dump() + payload.setdefault("_method", http_method) + result = await worker_manager.call( + worker_type=worker_type, + subject=subject, + payload=payload, + timeout=timeout, + cacheable=cacheable, + ) + return result + except RuntimeError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + return endpoint + + +def _make_query_endpoint(worker_manager: WorkerManager, rc: RouteConfig, worker_type: str, path_params: List[str], query_params: Dict[str, str]): + """ + 创建用于 Query 请求(GET/DELETE)的端点函数。 + + 通过设置函数 __signature__ 和 __annotations__ 为每个参数赋予正确的 Pydantic 类型, + 使 FastAPI 能在 OpenAPI schema 中生成正确的 type / format 信息。 + 请求模型 schema 通过 extra_schemas 机制单独注入到 components/schemas。 + """ + path = rc.path + subject = rc.subject + timeout = rc.timeout + http_method = rc.method + cacheable = rc.cacheable + + # 合并路径参数和查询参数 + param_spec_combined: Dict[str, str] = {} + param_spec_combined.update({k: query_params.get(k, "str") for k in path_params if k in query_params}) + param_spec_combined.update(query_params) + + # 无参数情况 + if not param_spec_combined: + async def _simple_endpoint() -> Any: + try: + result = await worker_manager.call( + worker_type=worker_type, + subject=subject, + payload={"_method": http_method}, + timeout=timeout, + cacheable=cacheable, + ) + return result + except RuntimeError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + return _simple_endpoint + + # 构造参数名列表(用于从 kwargs 中提取) + param_names = list(param_spec_combined.keys()) + + async def endpoint(**kwargs: Any) -> Any: + payload = {k: kwargs[k] for k in param_names} + payload.setdefault("_method", http_method) + try: + result = await worker_manager.call( + worker_type=worker_type, + subject=subject, + payload=payload, + timeout=timeout, + cacheable=cacheable, + ) + return result + except RuntimeError as e: + raise HTTPException(status_code=503, detail=str(e)) + except Exception as e: + logger.error(f"路由 {path} 调用异常: {e}") + raise HTTPException(status_code=500, detail="内部服务器错误") + + # 构建带类型注解的 Parameter 列表 + params: List[Parameter] = [] + annotations: Dict[str, Any] = {} + for name, type_str in param_spec_combined.items(): + py_type = _parse_python_type(type_str) + annotations[name] = py_type + if name in path_params: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=FastAPIPath(...), annotation=py_type)) + else: + params.append(Parameter(name, Parameter.KEYWORD_ONLY, default=Query(...), annotation=py_type)) + + # 设置 __annotations__(FastAPI 通过 get_type_hints 读取) + endpoint.__annotations__ = annotations + # 设置 __signature__(FastAPI 优先于此获取参数列表) + endpoint.__signature__ = Signature(params, return_annotation=Dict[str, Any]) # type: ignore + + return endpoint + + +def _register_single_route( + router: APIRouter, + worker_manager: WorkerManager, + worker_type: str, + route_conf: Dict[str, Any], + extra_schemas: Optional[Dict[str, type[BaseModel]]] = None, +): + """注册单个路由,生成正确的函数签名以便 OpenAPI 正确生成文档""" + rc = RouteConfig(route_conf) + param_spec = rc.parse_params() + path_params = rc.extract_path_params() + query_params = {k: v for k, v in param_spec.items() if k not in path_params} + + # 生成干净的模型名称(替换路径中的 / 和 {} 为 _) + # 模型名称包含 HTTP method 以区分同名路径的不同调用类型 + clean_path = rc.path.replace("/", "_").replace("{", "").replace("}", "").strip("_") + if not clean_path: + clean_path = "root" + method_tag = rc.method.upper() + response_model_name = f"{worker_type}_{clean_path}_{method_tag}_Response" + request_model_name = f"{worker_type}_{clean_path}_{method_tag}_Request" + + # 生成响应模型 + ResponseModel = generate_response_model( + rc.response_model, response_model_name + ) + + if rc.method in ("POST", "PUT", "PATCH"): + RequestModel = generate_request_model( + param_spec, request_model_name + ) + endpoint = _make_body_endpoint(worker_manager, rc, worker_type, RequestModel) + else: + # 对 GET 端点也生成 Request 模型并注册到 extra_schemas + if param_spec: + RequestModel = generate_request_model( + param_spec, request_model_name + ) + if extra_schemas is not None and request_model_name not in extra_schemas: + extra_schemas[request_model_name] = RequestModel + endpoint = _make_query_endpoint(worker_manager, rc, worker_type, path_params, query_params) + + # 注册路由 - 使用 worker_type 作为 OpenAPI 分组标签 + # 每个 worker 类型自动成为一个独立的分组,显示在 Swagger UI 中 + router.add_api_route( + path=rc.path, + endpoint=endpoint, + methods=[rc.method], + summary=rc.summary, + description=rc.docstring, + response_model=ResponseModel, + tags=[worker_type], + include_in_schema=True, + ) + logger.info(f"已注册动态路由: {rc.method} {rc.path} -> worker {worker_type}.{rc.subject} | tags=[{worker_type}]") diff --git a/src/chongming/app/core/static_files.py b/src/chongming/app/core/static_files.py new file mode 100644 index 0000000..0880cbe --- /dev/null +++ b/src/chongming/app/core/static_files.py @@ -0,0 +1,100 @@ +from fastapi import HTTPException +from fastapi.responses import Response +from fastapi.staticfiles import StaticFiles +from sqlite_vfs.core import SQLiteVFS + + +class SVFSStaticFiles(StaticFiles): + """ + 基于 SQLite VFS 的静态文件服务类 + + 该类继承自 FastAPI 的 StaticFiles,使用 SQLite 虚拟文件系统来存储和提供静态文件。 + 支持单页应用(SPA)的路由回退机制,当请求的文件不存在时,会自动返回 index.html。 + """ + + def __init__(self, directory, vfs_db_path="static.svfs"): + """ + 初始化 SVFSStaticFiles 实例 + + 参数: + directory (str): 静态文件的根目录路径 + vfs_db_path (str): SQLite VFS 数据库文件路径,默认为 "static.svfs" + + 返回值: + 无 + """ + vfs_db_path = vfs_db_path + self._vfs = SQLiteVFS(vfs_db_path, compress=False) + super().__init__(directory=directory) + + async def get_response(self, path: str, scope): + """ + 根据请求路径获取对应的静态文件响应 + + 该方法处理静态文件的请求逻辑,包括: + 1. 路径标准化处理 + 2. 文件存在性检查 + 3. SPA 路由回退支持 + 4. 目录索引处理 + 5. MIME 类型自动识别 + + 参数: + path (str): 请求的文件路径 + scope: ASGI 作用域对象,包含请求上下文信息 + + 返回值: + Response: FastAPI 响应对象,包含文件内容和正确的 Content-Type + + 异常: + HTTPException: 当文件不存在且无法回退到 index.html 时,抛出 404 错误 + """ + # 统一路径格式:以 / 开头,并将 Windows 反斜杠转为正斜杠 + normalized_path = "/" + path.replace("\\", "/") + + # 获取文件信息 + file_info = self._vfs.get_file_info(normalized_path) + + # 处理文件不存在的情况,尝试 SPA 回退机制 + if file_info is None: + # SPA 回退:如果不是静态资源扩展名,尝试返回 index.html + if not any( + normalized_path.endswith(ext) + for ext in [ + ".js", + ".css", + ".png", + ".jpg", + ".ico", + ".svg", + ".webp", + ".json", + ] + ): + index_info = self._vfs.get_file_info("/index.html") + if index_info: + content = self._vfs.read_file("/index.html") + return Response(content=content, media_type="text/html") + raise HTTPException(status_code=404, detail="File not found") + + # 处理目录请求,返回目录下的 index.html + if file_info["is_directory"]: + index_info = self._vfs.get_file_info(f"{normalized_path}/index.html") + if index_info: + content = self._vfs.read_file(f"{normalized_path}/index.html") + return Response(content=content, media_type="text/html") + raise HTTPException(status_code=404, detail="Directory index not found") + + # 读取文件内容并确定 MIME 类型 + content = self._vfs.read_file(normalized_path) + content_type = file_info.get("content_type") or "application/octet-stream" + # 简单根据扩展名补充 MIME 类型(可选) + if normalized_path.endswith(".js"): + content_type = "application/javascript" + elif normalized_path.endswith(".css"): + content_type = "text/css" + elif normalized_path.endswith(".html"): + content_type = "text/html" + elif normalized_path.endswith(".ico"): + content_type = "image/x-icon" + + return Response(content=content, media_type=content_type) diff --git a/src/chongming/app/core/worker_manager.py b/src/chongming/app/core/worker_manager.py new file mode 100644 index 0000000..a6a21cd --- /dev/null +++ b/src/chongming/app/core/worker_manager.py @@ -0,0 +1,202 @@ +import asyncio +import random +import json +import orjson +import time +from typing import Any, Dict, List, Optional, Set +import zenoh +from diskcache import Cache +from .logger import get_logger + +logger = get_logger("worker_manager") + +_SENTINEL = object() # 哨兵值,用于标记"No reply received" + + +class WorkerManager: + """ + 统一 Worker 管理器 + + 封装 Zenoh 会话,维护每个 worker 类型的存活节点列表, + 提供负载均衡、超时重试、缓存等能力的统一 RPC 调用入口。 + + 支持两种 worker 注册方式: + 1. 分布式:通过 Zenoh liveliness 自动发现 + 2. 本地:通过 register_worker() 直接注册(用于同进程 worker) + """ + + def __init__(self, session: zenoh.Session, cache: Optional[Cache] = None): + self.session = session + self.cache = cache + self._workers_cache: Dict[str, List[str]] = {} # type -> list of worker key_exprs + self._local_workers: Dict[str, List[str]] = {} # type -> list of locally registered workers + self._lock = asyncio.Lock() + self._refresh_interval = 15 # seconds + + def register_worker(self, worker_type: str, worker_id: str): + """ + 直接注册一个本地 worker(同进程内使用) + 这样无需等待 liveliness 发现,立即可用。 + + 同步执行(仅修改内存字典),确保调用后立即可被 get_worker 发现。 + """ + key_expr = f"worker/{worker_type}/{worker_id}" + # 同步直接写入 _local_workers,无需异步锁 + # 因为 _local_workers 和 _workers_cache 在调用 call() 时通过 await get_worker() 读取 + self._local_workers.setdefault(worker_type, []).append(key_expr) + logger.debug(f"本地 worker 已注册: {key_expr}") + + def unregister_worker(self, worker_type: str, worker_id: str): + """取消注册一个本地 worker""" + key_expr = f"worker/{worker_type}/{worker_id}" + if worker_type in self._local_workers: + try: + self._local_workers[worker_type].remove(key_expr) + except ValueError: + pass + + async def refresh_workers(self): + """定期刷新存活 worker 列表(合并 Zenoh liveliness + 本地注册)""" + while True: + try: + # 1. 从 Zenoh liveliness 获取分布式 workers + replies = self.session.liveliness().get("worker/*/*") + new_cache: Dict[str, List[str]] = {} + for reply in replies: + if reply.ok: + key = str(reply.ok.key_expr) # e.g. "worker/example/worker-0" + parts = key.split("/") + if len(parts) >= 3: + worker_type = parts[1] + new_cache.setdefault(worker_type, []).append(key) + + # 2. 合并本地注册的 workers + for wtype, workers in self._local_workers.items(): + existing = set(new_cache.get(wtype, [])) + for w in workers: + if w not in existing: + new_cache.setdefault(wtype, []).append(w) + + async with self._lock: + self._workers_cache = new_cache + logger.debug(f"Worker 存活列表更新: {self._workers_cache}") + except Exception as e: + logger.error(f"刷新 worker 列表失败: {e}") + await asyncio.sleep(self._refresh_interval) + + async def get_worker(self, worker_type: str) -> Optional[str]: + """获取一个可用的 worker 地址(简单随机负载均衡)""" + async with self._lock: + workers = self._workers_cache.get(worker_type, []) + # 如果缓存为空,回退到本地注册表(适用于启动初期 refresh_workers 尚未执行时) + if not workers: + local = self._local_workers.get(worker_type, []) + if local: + return random.choice(local) + return None + return random.choice(workers) + + def is_success_response(self, result: dict) -> bool: + """根据业务约定判断响应是否成功。 + 假设成功响应没有 'error' 字段,或 'code' 为 0。 + """ + if "error" in result and result["error"]: + return False + if "code" in result and result["code"] != 0: + return False + return True + + async def call( + self, + worker_type: str, + subject: str, + payload: Dict[str, Any], + timeout: float = 3.0, + retries: int = 1, + cacheable: bool = False, + ) -> Any: + """ + 统一调用入口:缓存 > 负载均衡 > 超时重试 + + Args: + worker_type: Worker 类型标识(如 "example") + subject: 调用的主题(如 "calc.add") + payload: 请求参数 + timeout: 单次调用超时(秒) + retries: 失败重试次数 + cacheable: 是否允许缓存结果(若为 False 则跳过缓存读写) + + Returns: + Dict[str, Any]: 调用结果 + + Raises: + RuntimeError: 无可用 worker 或所有重试均失败 + """ + # 1. 缓存检查(仅在 cacheable=True 时检查) + cache_key = f"{worker_type}:{subject}:{json.dumps(payload, sort_keys=True)}" + if cacheable and self.cache is not None: + if cache_key in self.cache: + logger.debug(f"命中缓存: {cache_key}") + return self.cache[cache_key] + + # 2. 获取可用 worker + worker_expr = await self.get_worker(worker_type) + if not worker_expr: + raise RuntimeError(f"No available worker for type {worker_type}") + + full_key = f"{worker_expr}/{subject}" + payload_bytes = orjson.dumps(payload) + + for attempt in range(retries + 1): + try: + reply_iter = self.session.get( + full_key, + payload=payload_bytes, + consolidation=zenoh.ConsolidationMode.NONE, + ) + # 使用辅助函数获取第一个回复,避免 StopIteration 在 asyncio.to_thread 中传播的问题 + def _get_first(iterator): + """线程安全地获取迭代器的第一个元素""" + for item in iterator: + return item + return _SENTINEL + + first_reply = await asyncio.wait_for( + asyncio.to_thread(_get_first, reply_iter), + timeout=timeout, + ) + if first_reply is _SENTINEL: + logger.warning( + f"Worker {worker_expr} 无回复,尝试重试 {attempt+1}/{retries+1}" + ) + continue + + reply = first_reply + if reply.ok: # type: ignore + # result_str = str(reply.ok.payload) + payload_bytes = bytes(reply.ok.payload) if reply.ok.payload else b"" # type: ignore + if not payload_bytes: + result = {} # 或根据业务返回默认值 + else: + result = orjson.loads(payload_bytes) + # 缓存结果(仅在 cacheable=True 时写入) + if cacheable and self.cache is not None and self.is_success_response(result): + self.cache[cache_key] = result + return result + else: + error_payload = reply.err.payload # type: ignore + error_msg = str(error_payload) if error_payload else "unknown error" + logger.warning(f"Worker 返回错误: {error_msg} (尝试 {attempt+1}/{retries+1})") + continue # 重试 + except asyncio.TimeoutError: + logger.warning( + f"Worker {worker_expr} 超时,尝试重试 {attempt+1}/{retries+1}" + ) + continue + except Exception as e: + logger.error(f"调用 worker 异常: {e}") + raise + + raise RuntimeError( + f"Failed to call worker {worker_type} after {retries} retries" + ) diff --git a/src/chongming/app/routes/__init__.py b/src/chongming/app/routes/__init__.py new file mode 100644 index 0000000..acfd551 --- /dev/null +++ b/src/chongming/app/routes/__init__.py @@ -0,0 +1,3 @@ +from .storage import router as storage_router + +__all__ = ["storage_router"] \ No newline at end of file diff --git a/src/chongming/app/routes/storage.py b/src/chongming/app/routes/storage.py new file mode 100644 index 0000000..200fd41 --- /dev/null +++ b/src/chongming/app/routes/storage.py @@ -0,0 +1,63 @@ +# src/chongming/app/routes/storage.py +import uuid +import urllib.parse +from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Request +from fastapi.responses import StreamingResponse +from botocore.exceptions import ClientError + +router = APIRouter(prefix="/api/v1/storage", tags=["Storage"]) + + +@router.post("/upload", summary="上传文件") +async def upload_file( + request: Request, + file: UploadFile = File(...), + worker_type: str = Form(...), +): + """后端代理上传,直接写入 nanio""" + s3_client = request.app.state.s3_client + bucket = request.app.state.storage_bucket + object_key = f"{worker_type}/{uuid.uuid4()}/{file.filename}" + + try: + s3_client.upload_fileobj( + file.file, + bucket, + object_key, + ExtraArgs={"ContentType": file.content_type or "application/octet-stream"} + ) + except ClientError as e: + raise HTTPException(status_code=500, detail=f"S3 上传失败: {str(e)}") + + return { + "file_key": object_key, + "bucket": bucket, + "filename": file.filename, + "content_type": file.content_type, + "size": file.size or 0, # 注意:UploadFile.size 可能为 None,需读取后计算 + } + + +@router.get("/download/{file_key:path}") +async def download_file(request: Request, file_key: str): + s3_client = request.app.state.s3_client + bucket = request.app.state.storage_bucket + + try: + response = s3_client.get_object(Bucket=bucket, Key=file_key) + content_type = response.get('ContentType', 'application/octet-stream') + raw_filename = file_key.split('/')[-1] + # 百分号编码,确保仅 ASCII 字符 + encoded = urllib.parse.quote(raw_filename, safe='') + # 仅使用 filename*(现代浏览器都支持) + content_disposition = f"attachment; filename*=UTF-8''{encoded}" + + return StreamingResponse( + response['Body'].iter_chunks(), + media_type=content_type, + headers={'Content-Disposition': content_disposition} + ) + except ClientError as e: + if e.response['Error']['Code'] == 'NoSuchKey': + raise HTTPException(404, "文件不存在") + raise HTTPException(500, f"下载失败: {e}") \ No newline at end of file diff --git a/src/chongming/scripts/build.py b/src/chongming/scripts/build.py new file mode 100644 index 0000000..e9f8d8d --- /dev/null +++ b/src/chongming/scripts/build.py @@ -0,0 +1,503 @@ +""" +构建脚本 - 将 chongming 应用打包为可执行文件 + +功能阶段: + 1. 模块打包(module-bank): 加密打包 Python 模块 + 2. 资源准备: 创建 build 目录, 复制必要文件 + 3. 混淆与打包: pyarmor 混淆 + PyInstaller/Nuitka 打包 + 4. 数据库初始化: 创建表结构和默认数据 + 5. 前端构建: 编译 Vue 项目并打包为 VFS + +用法: + python -m chongming.scripts.build [pyinstaller|nuitka] +""" + +import json +import ast +import platform +import shutil +import subprocess +import sys +import asyncio +import time +from dataclasses import dataclass, field +from pathlib import Path + +from module_bank import PythonToSQLite +from module_bank.encryption import Encryption +from sqlite_vfs.core import SQLiteVFS +from sqlite_vfs.folder_packer import FolderPacker +from ..app.core.config import get_config, Config + +# ── 常量 ──────────────────────────────────────────────────────────────────── + +PYINSTALLER = "pyinstaller" +NUITKA = "nuitka" +MODES = (PYINSTALLER, NUITKA) + +is_windows = platform.system() == "Windows" + +# ── 构建结果数据结构 ───────────────────────────────────────────────────────── + + +@dataclass +class BuildStepResult: + """单个构建步骤的执行结果""" + + name: str # 步骤名称 + success: bool # 是否成功 + duration: float # 耗时(秒) + message: str = "" # 附加信息(如包大小) + + +@dataclass +class BuildReport: + """构建总结报告""" + + mode: str + start_time: float + end_time: float = 0.0 + steps: list[BuildStepResult] = field(default_factory=list) + + @property + def total_duration(self) -> float: + return self.end_time - self.start_time if self.end_time else 0.0 + + @property + def all_success(self) -> bool: + return all(s.success for s in self.steps) + + def add_step(self, name: str, success: bool, duration: float, message: str = ""): + self.steps.append(BuildStepResult(name, success, duration, message)) + + def print_summary(self): + """打印结构化的构建总结""" + sep = "=" * 60 + print(f"\n{sep}") + print(f" 🏗️ CHONGMING 构建报告") + print(f"{sep}") + print(f" 构建模式 : {self.mode}") + print(f" 总耗时 : {self.total_duration:.2f}s") + print(f" 构建状态 : {'✅ 成功' if self.all_success else '❌ 失败'}") + print(f"{sep}") + print(f" 步骤明细:") + for i, step in enumerate(self.steps, 1): + icon = "✅" if step.success else "❌" + msg = f" ({step.message})" if step.message else "" + print( + f" {icon} [{i}/{len(self.steps)}] {step.name:<20s} {step.duration:>7.2f}s{msg}" + ) + print(f"{sep}\n") + + +# ── 工具函数 ──────────────────────────────────────────────────────────────── + + +def _run_cmd( + cmd: list[str], + cwd: str | None = None, + capture: bool = True, + shell: bool | None = None, +) -> subprocess.CompletedProcess: + """统一执行外部命令""" + if shell is None: + shell = is_windows + return subprocess.run( + cmd, + cwd=cwd, + capture_output=capture, + text=True, + shell=shell, + encoding="utf-8", + errors="replace", + ) + + +def _format_size(path: str | Path) -> str: + """获取文件可读大小""" + size = Path(path).stat().st_size + for unit in ("B", "KB", "MB", "GB"): + if size < 1024: + return f"{size:.2f} {unit}" + size /= 1024.0 + return f"{size:.2f} TB" + + +def _ensure_dir(path: str | Path): + """确保目录存在""" + Path(path).mkdir(parents=True, exist_ok=True) + + +def _resolve_mode() -> str: + """解析构建模式""" + mode = PYINSTALLER + if len(sys.argv) >= 2: + mode = sys.argv[1].lower() + if mode not in MODES: + print(f" ❌ 不支持的构建模式: {mode},可选: {', '.join(MODES)}") + sys.exit(1) + return mode + + +# ── 阶段 1: 模块打包 (module-bank) ────────────────────────────────────────── + + +def _stage_pack_modules(config: Config) -> str: + """ + 使用 module-bank 加密打包 Python 模块为 .mbank 文件。 + 返回打包过程中的提示信息。 + """ + _ensure_dir("resources") + mbank_list = config.get("production", {}).get("module_system", {}).get("path", []) + if not mbank_list: + return "⚠️ 未配置 module_system.path,跳过模块打包" + + messages = [] + for output_path, source_dir, package_name in mbank_list: + key = Encryption.generate_key() + packer = PythonToSQLite( + output_path, + key, + pyarmor_obfuscate=True, + ) + packer.pack_directory(source_dir, package_name) + packer.verify_package_structure() + packer.packer_key(key) + packer.delete_source_code(None) + size = _format_size(output_path) + messages.append(f"{Path(output_path).name} ({size})") + return " - " + "\n - ".join(messages) + + +def _stage_prepare_build_dir(config: Config, mode: str) -> str: + """准备 build 目录并复制必要文件""" + build_path = Path("build") + if build_path.exists(): + shutil.rmtree(str(build_path)) + _ensure_dir("build") + + # 复制 utils + utils_src = Path("public/utils") + if utils_src.exists(): + shutil.copytree(utils_src, build_path / "utils") + + # 复制 resources + resources_src = Path("resources") + if resources_src.exists(): + shutil.copytree(resources_src, build_path / "resources") + + resources_src = Path("config") + if resources_src.exists(): + shutil.copytree(resources_src, build_path / "config") + + # 复制核心文件 + for f in ("main.py", "server.py", "config.toml"): + shutil.copy(f"public/{f}", f"build/{f}") + + # 注入 JWT 密钥 + jwt_key = Encryption.generate_key() + cfg_path = build_path / "config.toml" + content = cfg_path.read_text(encoding="utf-8") + content = content.replace("your-secret-key-change-in-production", jwt_key) + cfg_path.write_text(content, encoding="utf-8") + + return "资源准备完成" + + +# ── 阶段 3: PyArmor 混淆 ─────────────────────────────────────────────────── + + +def _stage_pyarmor_obfuscate() -> str: + """对关键脚本执行 PyArmor 混淆""" + files = [ + ("main.py", "./"), + ("server.py", "./"), + ("utils/launch.py", "./utils"), + ("utils/config.py", "./utils"), + ] + failed = [] + for filename, output in files: + result = _run_cmd( + ["pyarmor", "gen", f"--output={output}", filename], + cwd="build", + ) + if result.returncode != 0: + failed.append(filename) + if failed: + return f"❌ 混淆失败: {', '.join(failed)}" + return "✅ 所有文件混淆成功" + + +# ── 阶段 4: PyInstaller 打包 ──────────────────────────────────────────────── + + +def _stage_pyinstaller(config: Config) -> tuple[str, str]: + """执行 PyInstaller 打包,返回 (状态消息, 可执行文件路径)""" + base_imports = config.get("pyinstaller_build_import", {}) + build_imports = list(base_imports.get("base", [])) + build_imports.extend( + base_imports.get("windows", []) if is_windows else base_imports.get("linux", []) + ) + + icon = Path("../../public/chongming.ico") + cmd = [ + sys.executable, + "-m", + "PyInstaller", + "--onefile", + "--noconfirm", + "--optimize", + "2", + "--name", + "chongming", + "--distpath", + ".", + "--workpath", + "./build_temp", + "--specpath", + "./specs", + "--clean", + ] + if not is_windows: + cmd.append("--strip") + if icon.exists(): + cmd.extend(["--icon", str(icon)]) + for imp in build_imports: + cmd.extend(["--hidden-import", imp]) + + cmd.append("main.py") + + result = _run_cmd(cmd, cwd="build", capture=True) + if result.returncode != 0: + return f"❌ PyInstaller 失败", "" + + exe_path = Path("build/chongming") + if is_windows: + exe_path = exe_path.with_suffix(".exe") + + suffix = ".exe" if is_windows else "" + exe = Path("build") / f"chongming{suffix}" + if exe.exists(): + size = _format_size(exe) + return f"✅ PyInstaller 打包成功 ({size})", str(exe) + return "✅ PyInstaller 打包完成 (但未找到可执行文件)", "" + + +# ── 阶段 5: Nuitka 打包 ──────────────────────────────────────────────────── + + +def _stage_nuitka(config: Config) -> tuple[str, str]: + """执行 Nuitka 打包,返回 (状态消息, 可执行文件路径)""" + base_imports = config.get("pyinstaller_build_import", {}).get("base", []) + extra = config.get("pyinstaller_build_import", {}).get( + "windows" if is_windows else "linux", [] + ) + + packages = [ + "module_bank", + "sqlite_vfs", + "fastapi", + "uvicorn", + "gunicorn", + "diskcache", + "apscheduler", + "tomli", + "typing_extensions", + ] + + cmd = [ + sys.executable, + "-m", + "nuitka", + "--standalone", + "--onefile", + "--follow-imports", + "--static-libpython=no", + "--remove-output", + "--assume-yes-for-downloads", + "--lto=yes", + "--disable-plugin=tk-inter", + "--disable-plugin=numpy", + "--output-dir=.", + "--output-filename=chongming", + "main.py", + "--jobs=2", + ] + if shutil.which("upx"): + cmd.append("--enable-plugin=upx") + for pkg in packages: + cmd.append(f"--include-package={pkg}") + for mod in list(base_imports) + extra: + cmd.append(f"--include-module={mod}") + + if is_windows: + icon = Path("../../public/chongming.ico") + if icon.exists(): + cmd.append(f"--windows-icon-from-ico={icon}") + + try: + _run_cmd(cmd, cwd="build", capture=False) + except subprocess.CalledProcessError: + # 降级重试(移除可能不兼容的参数) + fallback = [ + arg for arg in cmd if arg not in ("--lto=yes", "--enable-plugin=upx") + ] + _run_cmd(fallback, cwd="build", capture=False) + + exe = Path("build/chongming") + if exe.exists(): + # 后处理瘦身 + if not is_windows: + subprocess.run(["strip", str(exe)], check=False) + if shutil.which("upx"): + subprocess.run(["upx", "--best", str(exe)], check=False) + size = _format_size(exe) + return f"✅ Nuitka 编译完成 ({size})", str(exe) + + return "❌ Nuitka 编译失败 (未生成可执行文件)", "" + + +# ── 阶段 6: 清理临时文件 ──────────────────────────────────────────────────── + + +def _stage_cleanup(mode: str) -> str: + """清理构建过程中的临时文件""" + build = Path("build") + dirs_to_remove = [ + "build_temp", + "specs", + "utils", + "pyarmor_runtime_000000", + "__pycache__", + ] + files_to_remove = ["main.py", "server.py"] + + for d in dirs_to_remove: + target = build / d + if target.exists(): + shutil.rmtree(str(target), ignore_errors=True) + + for f in files_to_remove: + target = build / f + if target.exists(): + target.unlink(missing_ok=True) + + return "临时文件已清理" + +# ── 阶段 8: Vue 前端构建 ──────────────────────────────────────────────────── + + +def _stage_build_vue() -> str: + """构建 Vue 前端并打包为 SQLite VFS""" + dist_dir = "./src/chongming-web/dist" + output_db = "./build/static.svfs" + fs_name = "frontend" + + # 1. npm build + result = _run_cmd( + ["npm", "run", "build"], + cwd="./src/chongming-web", + capture=True, + ) + if result.returncode != 0: + err = (result.stderr or "").strip()[:200] + return f"❌ Vue 构建失败: {err}" + + # 2. 检查产物 + if not Path(dist_dir).exists(): + return "❌ Vue 构建成功但未找到 dist 目录" + + # 3. 打包为 VFS + vfs = SQLiteVFS(output_db, compress=False) + packer = FolderPacker(sqlite_vfs=vfs, exclude_patterns=[], fs_name=fs_name) + try: + packer.pack_folder(dist_dir) + finally: + vfs.close() + + size = _format_size(output_db) + return f"✅ Vue 打包完成 ({size})" + + +# ── 主流程 ────────────────────────────────────────────────────────────────── + + +async def main(): + """规范化构建主流程""" + mode = _resolve_mode() + report = BuildReport(mode=mode, start_time=time.time()) + config = get_config() + + async def run_step(name: str, fn, *args, **kwargs) -> str: + """执行一个构建步骤并记录结果(支持同步和异步函数)""" + start = time.time() + try: + result = fn(*args, **kwargs) + if asyncio.iscoroutine(result): + msg = await result + else: + msg = result + elapsed = time.time() - start + report.add_step(name, success=True, duration=elapsed, message=msg or "") + print(f" ✅ {name:<25s} {elapsed:>7.2f}s {msg}") + return msg + except Exception as e: + elapsed = time.time() - start + err_msg = str(e).strip()[:200] + report.add_step(name, success=False, duration=elapsed, message=err_msg) + print(f" ❌ {name:<25s} {elapsed:>7.2f}s {err_msg}") + return "" + + print("\n" + "=" * 60) + print(" 🏗️ CHONGMING 构建开始") + print(f" 模式: {mode} | 平台: {platform.system()}") + print("=" * 60 + "\n") + + # 阶段 1: 模块打包 + await run_step("模块打包(module-bank)", _stage_pack_modules, config) + + # 阶段 2: 准备构建目录 + await run_step("准备构建目录", _stage_prepare_build_dir, config, mode) + + # 阶段 3+4: 混淆 + 打包 + if mode == PYINSTALLER: + await run_step("PyArmor 混淆", _stage_pyarmor_obfuscate) + start = time.time() + try: + msg, exe_path = _stage_pyinstaller(config) + elapsed = time.time() - start + success = "❌" not in msg + report.add_step( + "PyInstaller 打包", success, elapsed, msg.replace("✅ PyInstaller ", "") + ) + print( + f" {'✅' if success else '❌'} PyInstaller 打包{' ' * 12} {elapsed:>7.2f}s {msg}" + ) + except Exception as e: + elapsed = time.time() - start + report.add_step("PyInstaller 打包", False, elapsed, str(e)[:200]) + print(f" ❌ PyInstaller 打包 {elapsed:>7.2f}s {str(e)[:200]}") + else: + await run_step("Nuitka 编译", lambda: _stage_nuitka(config)[0]) + + # 阶段 5: 清理 + await run_step("清理临时文件", _stage_cleanup, mode) + + # 阶段 7: Vue 前端构建 + await run_step("Vue 前端构建", _stage_build_vue) + + # 总结 + report.end_time = time.time() + report.print_summary() + + if not report.all_success: + sys.exit(1) + + +def build(): + """构建入口""" + asyncio.run(main()) + + +if __name__ == "__main__": + build() diff --git a/src/chongming/scripts/code_content.py b/src/chongming/scripts/code_content.py new file mode 100644 index 0000000..a5e69fc --- /dev/null +++ b/src/chongming/scripts/code_content.py @@ -0,0 +1,47 @@ +import os +import sys + + +def generate_markdown_from_py_files(directory, output_file): + with open(output_file, "w", encoding="utf-8") as md_file: + for root, dirs, files in os.walk(directory): + # 排除 venv 目录 + dirs[:] = [d for d in dirs if d != ".venv"] + dirs[:] = [d for d in dirs if d != ".vscode"] + dirs[:] = [d for d in dirs if d != "node_modules"] + dirs[:] = [d for d in dirs if d != "build"] + dirs[:] = [d for d in dirs if d != "dist"] + dirs[:] = [d for d in dirs if d != "chongming-web"] + for file in files: + if ( + file.endswith(".py") + or file.endswith(".rs") + or file.endswith(".toml") + or file.endswith(".md") + or file.endswith(".css") + or file.endswith(".vue") + or file.endswith(".ts") + or file.endswith(".json") + or file.endswith(".html") + or file.endswith(".sh") + or file == "Dockerfile" + ): + file_path = os.path.join(root, file) + if file == "output.md" or file == "package-lock.json": + continue + if "generated" in file_path: + continue + md_file.write(f"`{file_path}`\n") + md_file.write("```\n") + with open(file_path, "r", encoding="utf-8") as py_file: + md_file.write(py_file.read()) + md_file.write("\n```\n\n") + + +if __name__ == "__main__": + # 指定目录和输出文件名 + target_directory = sys.argv[1] # 替换为你的目标目录 + output_markdown_file = "output.md" # 输出的 Markdown 文件名 + + generate_markdown_from_py_files(target_directory, output_markdown_file) + print(f"Markdown 文件已生成:{output_markdown_file}") diff --git a/src/chongming/scripts/gen_models.py b/src/chongming/scripts/gen_models.py new file mode 100644 index 0000000..77d5983 --- /dev/null +++ b/src/chongming/scripts/gen_models.py @@ -0,0 +1,333 @@ +""" +模型生成脚本 — 从 config/workers//*.toml 生成 Pydantic 模型 + +用法: + uv run gen_models config/workers/example src/plugins/example + +工作流: + 1. 编写 config/workers//*.toml 配置文件 + 2. 执行 gen_models 生成 Pydantic 模型 + 3. 在业务代码中 import 生成的模型 + +功能: + 1. 扫描配置目录下的所有 .toml 文件 + 2. 解析每个路由的 params 和 response_model + 3. 生成 Pydantic 请求/响应模型 + 4. 写入 src/plugins//models/__init__.py 和 models/pydantic.py +""" + +import sys +import json +from pathlib import Path +from typing import Any + + +def parse_type_str(type_str: str) -> str: + """将 TOML 中的类型字符串映射为 Python 类型注解""" + mapping = { + "float": "float", + "int": "int", + "str": "str", + "bool": "bool", + "string": "str", + "integer": "int", + "number": "float", + "boolean": "bool", + "any": "Any", + } + + if type_str.startswith("list[") and type_str.endswith("]"): + inner = type_str[5:-1] + inner_py = mapping.get(inner, inner) + return f"list[{inner_py}]" + + if type_str == "dict": + return "dict[str, Any]" + + if type_str.startswith("dict[") and type_str.endswith("]"): + inner = type_str[5:-1] + if "," in inner: + parts = [p.strip() for p in inner.split(",", 1)] + inner_py = mapping.get(parts[-1], parts[-1]) + else: + inner_py = mapping.get(inner, inner) + return f"dict[str, {inner_py}]" + + if type_str.startswith("Optional[") and type_str.endswith("]"): + inner = type_str[9:-1] + inner_py = mapping.get(inner, inner) + return f"Optional[{inner_py}]" + + return mapping.get(type_str, type_str) + + +def parse_params(params: list[str]) -> list[dict[str, str]]: + """解析 params 列表,如 ['a: float', 'b: float']""" + result = [] + for p in params: + if ":" in p: + name, type_str = p.split(":", 1) + result.append({"name": name.strip(), "type": parse_type_str(type_str.strip())}) + else: + result.append({"name": p.strip(), "type": "str"}) + return result + + +def parse_response_model(response_model: dict[str, Any]) -> list[dict[str, Any]]: + """解析 response_model 字典,返回字段列表""" + fields: list[dict[str, Any]] = [] + for field_name, field_spec in response_model.items(): + entry: dict[str, Any] = {"name": field_name} + if isinstance(field_spec, dict): + entry["type"] = parse_type_str(field_spec.get("type", "str")) + entry["required"] = field_spec.get("required", True) + if "default" in field_spec: + entry["default"] = field_spec["default"] + else: + entry["type"] = parse_type_str(str(field_spec)) + entry["required"] = True + fields.append(entry) + return fields + + +def _model_name(base: str, method: str, suffix: str) -> str: + """生成模型名: e.g. add + GET + Request -> AddGetRequest""" + return f"{base.title().replace('-', '').replace('_', '')}{method.title()}{suffix}" + + +def generate_pydantic_code( + plugin_name: str, + routes: list[dict[str, Any]], +) -> str: + """生成 pydantic.py 文件内容""" + lines = [] + lines.append('"""') + lines.append(f"自动生成的 Pydantic 模型 — {plugin_name}") + lines.append(f"来源: config/workers/{plugin_name}/") + lines.append('"""') + lines.append("") + lines.append("from __future__ import annotations") + lines.append("") + lines.append("from typing import Any, Optional") + lines.append("from pydantic import BaseModel, Field") + lines.append("") + + seen_models: set[str] = set() + + for route in routes: + route_name = route["name"] + method = route["method"] + summary = route.get("summary", "") + + # --- Request Model --- + params = route.get("parsed_params", []) + if params: + model_name = _model_name(route_name, method, "Request") + if model_name not in seen_models: + seen_models.add(model_name) + lines.append(f"class {model_name}(BaseModel):") + lines.append(f' """{summary} — 请求体"""') + for p in params: + lines.append(f" {p['name']}: {p['type']} = Field(...)") + lines.append("") + + # --- Response Model --- + response_fields = route.get("parsed_response", []) + if response_fields: + model_name = _model_name(route_name, method, "Response") + if model_name not in seen_models: + seen_models.add(model_name) + lines.append(f"class {model_name}(BaseModel):") + lines.append(f' """{summary} — 响应体"""') + for f in response_fields: + field_type = f["type"] + if f.get("required", True): + if "default" in f: + default_repr = ( + json.dumps(f["default"]) + if not isinstance(f["default"], str) + else f"'{f['default']}'" + ) + lines.append(f" {f['name']}: {field_type} = Field(default={default_repr})") + else: + lines.append(f" {f['name']}: {field_type} = Field(...)") + else: + if "default" in f: + default_repr = ( + json.dumps(f["default"]) + if not isinstance(f["default"], str) + else f"'{f['default']}'" + ) + lines.append(f" {f['name']}: Optional[{field_type}] = Field(default={default_repr})") + else: + lines.append(f" {f['name']}: Optional[{field_type}] = Field(default=None)") + lines.append("") + + # --- Union type for all responses --- + response_models = [m for m in seen_models if m.endswith("Response")] + if response_models: + plugin_title = plugin_name.title().replace("-", "").replace("_", "") + lines.append("") + lines.append("# 所有响应模型的联合类型") + lines.append(f"{plugin_title}Response = " + " | ".join(response_models)) + lines.append("") + + return "\n".join(lines) + + +def generate_init_code( + plugin_name: str, + routes: list[dict[str, Any]], +) -> str: + """生成 __init__.py 文件内容""" + lines = [] + lines.append('"""') + lines.append(f"模型导出 — {plugin_name}") + lines.append("自动生成,请勿手动修改") + lines.append('"""') + lines.append("") + + seen_models: set[str] = set() + for route in routes: + rname = route["name"] + method = route["method"] + if route.get("parsed_params"): + seen_models.add(_model_name(rname, method, "Request")) + if route.get("parsed_response"): + seen_models.add(_model_name(rname, method, "Response")) + + response_models = [m for m in seen_models if m.endswith("Response")] + if response_models: + plugin_title = plugin_name.title().replace("-", "").replace("_", "") + seen_models.add(f"{plugin_title}Response") + + for model_name in sorted(seen_models): + lines.append(f"from .pydantic import {model_name}") + + lines.append("") + lines.append("__all__ = [") + for model_name in sorted(seen_models): + lines.append(f' "{model_name}",') + lines.append("]") + lines.append("") + + return "\n".join(lines) + + +def scan_config_dir(config_dir: Path) -> list[dict[str, Any]]: + """扫描配置目录,解析所有 .toml 文件中的路由定义""" + import tomli + + routes = [] + toml_files = sorted(config_dir.glob("*.toml")) + + group_config = {} + config_file = config_dir / "config.toml" + if config_file.exists(): + with open(config_file, "rb") as f: + group_config = tomli.load(f) + + defaults = group_config.get("worker", {}).get("defaults", {}) + router_prefix = ( + group_config.get("worker", {}) + .get("registration", {}) + .get("router_prefix", "") + ) + + for toml_file in toml_files: + if toml_file.name == "config.toml": + continue + + with open(toml_file, "rb") as f: + data = tomli.load(f) + + route_name = toml_file.stem + + for method in ("GET", "POST", "PUT", "DELETE", "PATCH"): + if method not in data: + continue + + entry = data[method] + params_raw = entry.get("params", []) + response_raw = entry.get("response_model", {}) + + route = { + "name": route_name, + "method": method, + "summary": entry.get("summary", ""), + "docstring": entry.get("docstring", ""), + "params": params_raw, + "response_model": response_raw, + "parsed_params": parse_params(params_raw), + "parsed_response": parse_response_model(response_raw), + "path": entry.get("path", f"{router_prefix}/{route_name}"), + "timeout": entry.get("timeout", defaults.get("timeout", 3.0)), + "auth_required": entry.get("auth_required", defaults.get("auth_required", False)), + } + routes.append(route) + + return routes + + +def main(): + if len(sys.argv) < 3: + print("用法: uv run gen_models ") + print("示例:") + print(" uv run gen_models config/workers/example src/plugins/example") + sys.exit(1) + + config_dir = Path(sys.argv[1]) + plugin_dir = Path(sys.argv[2]) + plugin_name = plugin_dir.name + + if not config_dir.exists(): + print(f"❌ 配置目录不存在: {config_dir}") + sys.exit(1) + if not plugin_dir.exists(): + print(f"❌ 插件目录不存在: {plugin_dir}") + sys.exit(1) + + print(f"🔍 扫描配置目录: {config_dir}") + routes = scan_config_dir(config_dir) + print(f" 发现 {len(routes)} 个路由定义") + + if not routes: + print("⚠️ 未找到路由定义,跳过模型生成") + return + + models_dir = plugin_dir / "models" + models_dir.mkdir(parents=True, exist_ok=True) + + pydantic_code = generate_pydantic_code(plugin_name, routes) + pydantic_file = models_dir / "pydantic.py" + pydantic_file.write_text(pydantic_code, encoding="utf-8") + print(f"✅ 生成: {pydantic_file}") + + init_code = generate_init_code(plugin_name, routes) + init_file = models_dir / "__init__.py" + init_file.write_text(init_code, encoding="utf-8") + print(f"✅ 生成: {init_file}") + + print(f"\n📋 生成的模型:") + for route in routes: + method = route["method"] + name = route["name"] + params = route.get("parsed_params", []) + response = route.get("parsed_response", []) + if params: + req_model = _model_name(name, method, "Request") + param_strs = [f"{p['name']}: {p['type']}" for p in params] + print(f" 📥 {req_model}: {', '.join(param_strs)}") + if response: + resp_model = _model_name(name, method, "Response") + field_strs = [f"{f['name']}: {f['type']}" for f in response] + print(f" 📤 {resp_model}: {', '.join(field_strs)}") + + +def gen_models(): + """CLI 入口""" + main() + + +if __name__ == "__main__": + main() diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/plugins/example/__init__.py b/src/plugins/example/__init__.py new file mode 100644 index 0000000..7fbe9d1 --- /dev/null +++ b/src/plugins/example/__init__.py @@ -0,0 +1,146 @@ +""" +Example Worker 插件 — 演示 @rpc_method + 显式模型绑定用法 + +本插件展示如何使用 chongming_worker 框架定义 RPC 端点。 + +核心工作流: + 1. 编写 config/workers/example/*.toml 配置文件 + 2. 执行 uv run gen_models 生成 Pydantic 模型 + 3. 在 __init__.py 中用 @rpc_method 定义业务函数 + 4. 显式导入模型并绑定到对应端点(替代 auto_bind_models) + +关键约定: + - 模型命名: {Request|Response} + (如 AddGetRequest, AddGetResponse 由 gen_models 自动生成) + - 装饰器 subject 中的操作名 → 模型前缀(驼峰) + example.add → Add, example.subtract → Subtract +""" +import time + +from chongming_worker import rpc_method, auto_bind_models +from chongming_worker.runtime import create_workers as _create_workers + +# ============================================================ +# 显式导入 Pydantic 模型(避免 PyArmor 打包后 importlib 动态导入异常) +# ============================================================ +from .models.pydantic import ( + AddGetRequest, + AddGetResponse, + AddPostRequest, + AddPostResponse, + SubtractGetRequest, + SubtractGetResponse, + MultiplyGetRequest, + MultiplyGetResponse, + DivideGetRequest, + DivideGetResponse, +) + + +# ============================================================ +# 注册 RPC 端点 +# ============================================================ +# 直接在 @rpc_method 中指定 response_model(和可选的 request_model), +# 不再依赖 auto_bind_models 进行动态导入绑定。 +# 这样 PyArmor 打包后不会因 importlib.import_module 路径解析失败而出问题。 + + +@rpc_method( + subject="example.add", + method="GET", + summary="Add two numbers via GET", + response_model=AddGetResponse, +) +def add(add_get_request:AddGetRequest) -> AddGetResponse: + """加法运算(GET)— 通过查询参数传入 a、b,返回它们的和""" + return AddGetResponse( + result=add_get_request.a + add_get_request.b, + operation="add", + timestamp=time.time() + ) + +@rpc_method( + subject="example.add", + method="POST", + summary="Add two numbers via POST", + response_model=AddPostResponse, +) +def add_post(add_post_request: AddPostRequest) -> AddPostResponse: + """加法运算(POST)— 通过 JSON Body 传入 a、b,返回它们的和""" + return AddPostResponse( + result=add_post_request.a + add_post_request.b, + operation="add", + timestamp=time.time(), + ) + + +@rpc_method( + subject="example.subtract", + method="GET", + summary="Subtract two numbers", + response_model=SubtractGetResponse, +) +def subtract(subtract_get_request: SubtractGetRequest) -> SubtractGetResponse: + """减法运算:a - b""" + return SubtractGetResponse( + result=subtract_get_request.a - subtract_get_request.b, + operation="subtract", + timestamp=time.time(), + ) + + +@rpc_method( + subject="example.multiply", + method="GET", + summary="Multiply two numbers", + response_model=MultiplyGetResponse, +) +def multiply(multiply_get_request=MultiplyGetRequest) -> MultiplyGetResponse: + """乘法运算:a * b""" + return MultiplyGetResponse( + result=multiply_get_request.a * multiply_get_request.b, + operation="multiply", + timestamp=time.time(), + ) + +@rpc_method( + subject="example.divide", + method="GET", + summary="Divide two numbers", + response_model=DivideGetResponse, +) +def divide(divide_get_request: DivideGetRequest) -> DivideGetResponse: + """除法运算:a / b(b 不能为 0)""" + if divide_get_request.b == 0: + return DivideGetResponse( + error="除数不能为零", + operation="divide", + result=0.0, + timestamp=time.time(), + ) + return DivideGetResponse( + result=divide_get_request.a / divide_get_request.b, + operation="divide", + timestamp=time.time(), + ) + +# ============================================================ +# 自动绑定 Pydantic 模型(作为备用,但在显式绑定后实际不再需要) +# ============================================================ +# 保留 auto_bind_models 调用作为 fallback,确保任何未显式指定模型的端点 +# 也能自动匹配到合适的模型。由于大部分端点已在 @rpc_method 中显式指定, +# auto_bind_models 会跳过已绑定的端点。 +auto_bind_models("example") + + +# ============================================================ +# 工厂函数 — 供 app lifespan 动态加载 +# ============================================================ +# 框架通过 __import__ + getattr 获取此函数, +# 然后调用 create_workers(session, config, count, worker_manager=worker_manager)。 +# 详见 src/chongming/app/__init__.py 的 lifespan。 + + +def create_workers(session, config, count=1, worker_manager=None): + """插件入口 — 委托给 chongming_worker 运行时创建 Worker 协程""" + return _create_workers(session, config, count, worker_manager) diff --git a/src/plugins/example/models/__init__.py b/src/plugins/example/models/__init__.py new file mode 100644 index 0000000..987bfb4 --- /dev/null +++ b/src/plugins/example/models/__init__.py @@ -0,0 +1,30 @@ +""" +模型导出 — example +自动生成,请勿手动修改 +""" + +from .pydantic import AddGetRequest +from .pydantic import AddGetResponse +from .pydantic import AddPostRequest +from .pydantic import AddPostResponse +from .pydantic import DivideGetRequest +from .pydantic import DivideGetResponse +from .pydantic import ExampleResponse +from .pydantic import MultiplyGetRequest +from .pydantic import MultiplyGetResponse +from .pydantic import SubtractGetRequest +from .pydantic import SubtractGetResponse + +__all__ = [ + "AddGetRequest", + "AddGetResponse", + "AddPostRequest", + "AddPostResponse", + "DivideGetRequest", + "DivideGetResponse", + "ExampleResponse", + "MultiplyGetRequest", + "MultiplyGetResponse", + "SubtractGetRequest", + "SubtractGetResponse", +] diff --git a/src/plugins/example/models/pydantic.py b/src/plugins/example/models/pydantic.py new file mode 100644 index 0000000..df483df --- /dev/null +++ b/src/plugins/example/models/pydantic.py @@ -0,0 +1,69 @@ +""" +自动生成的 Pydantic 模型 — example +来源: config/workers/example/ +""" + +from __future__ import annotations + +from typing import Any, Optional +from pydantic import BaseModel, Field + +class AddGetRequest(BaseModel): + """Add two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class AddGetResponse(BaseModel): + """Add two numbers — 响应体""" + result: float = Field(...) + operation: str = Field(default='add') + timestamp: Optional[float] = Field(default=0.0) + +class AddPostRequest(BaseModel): + """Add two numbers (POST) — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class AddPostResponse(BaseModel): + """Add two numbers (POST) — 响应体""" + result: float = Field(...) + operation: str = Field(default='add') + timestamp: Optional[float] = Field(default=0.0) + +class DivideGetRequest(BaseModel): + """Divide two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class DivideGetResponse(BaseModel): + """Divide two numbers — 响应体""" + result: Optional[float] = Field(default=0.0) + operation: str = Field(default='divide') + timestamp: Optional[float] = Field(default=0.0) + error: Optional[str] = Field(default='') + +class MultiplyGetRequest(BaseModel): + """Multiply two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class MultiplyGetResponse(BaseModel): + """Multiply two numbers — 响应体""" + result: float = Field(...) + operation: str = Field(default='multiply') + timestamp: Optional[float] = Field(default=0.0) + +class SubtractGetRequest(BaseModel): + """Subtract two numbers — 请求体""" + a: float = Field(...) + b: float = Field(...) + +class SubtractGetResponse(BaseModel): + """Subtract two numbers — 响应体""" + result: float = Field(...) + operation: str = Field(default='subtract') + timestamp: Optional[float] = Field(default=0.0) + + +# 所有响应模型的联合类型 +ExampleResponse = MultiplyGetResponse | AddGetResponse | SubtractGetResponse | AddPostResponse | DivideGetResponse diff --git a/test.mp4 b/test.mp4 new file mode 100644 index 0000000..0fe4de7 Binary files /dev/null and b/test.mp4 differ diff --git a/utils/request/README.md b/utils/request/README.md new file mode 100644 index 0000000..e69de29 diff --git a/utils/request/pyproject.toml b/utils/request/pyproject.toml new file mode 100644 index 0000000..afac911 --- /dev/null +++ b/utils/request/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +name = "chongming-request" +version = "0.1.0" +description = "worker direct communication" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "eclipse-zenoh>=1.9.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming_request"] + diff --git a/utils/request/src/chongming_request/__init__.py b/utils/request/src/chongming_request/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/request/src/chongming_request/client.py b/utils/request/src/chongming_request/client.py new file mode 100644 index 0000000..e77a8a6 --- /dev/null +++ b/utils/request/src/chongming_request/client.py @@ -0,0 +1,110 @@ +import asyncio +import logging +import random +import time +from typing import Optional, Dict, Any, List + +import zenoh +import orjson + + +_SENTINEL = object() + +class Client: + def __init__(self, + session: Optional[zenoh.Session] = None, + logger: Optional[logging.Logger] = None): + if session is None: + self._session = zenoh.open(zenoh.Config()) + self._owns_session = True + else: + self._session = session + self._owns_session = False + self._logger = logger or logging.getLogger(__name__) + self._worker_cache = {} + self._cache_ttl = 15 + self._cache_timestamps = {} + + async def _get_workers(self, worker_type: str) -> List[str]: + now = time.monotonic() + if worker_type in self._worker_cache: + if now - self._cache_timestamps[worker_type] < self._cache_ttl: + return self._worker_cache[worker_type] + try: + replies = self._session.liveliness().get(f"worker/{worker_type}/*") + workers = [str(r.ok.key_expr) for r in replies if r.ok] + self._worker_cache[worker_type] = workers + self._cache_timestamps[worker_type] = now + return workers + except Exception as e: + if worker_type in self._worker_cache: + self._logger.warning("Using expired cache") + return self._worker_cache[worker_type] + self._logger.error(str(e)) + raise ValueError(str(e)) + + async def call( + self, + worker_type: str, + subject: str, + payload: Dict[str, Any], + method: str = "GET", + timeout: float = 3.0, + retries: int = 1, + ) -> Dict[str, Any]: + payload.setdefault("_method", method.upper()) + full_key = "" + for attempt in range(retries + 1): + workers = await self._get_workers(worker_type) + if not workers: + raise RuntimeError(...) + target = random.choice(workers) + full_key = f"{target}/{subject}" + try: + reply_iter = self._session.get( + full_key, + payload=orjson.dumps(payload), + consolidation=zenoh.ConsolidationMode.NONE, + ) + def _get_first(iterator): + for item in iterator: + return item + return _SENTINEL + first_reply = await asyncio.wait_for( + asyncio.to_thread(_get_first, reply_iter), + timeout=timeout, + ) + if first_reply is _SENTINEL: + self._logger.warning(f"Worker {target} did not respond to '{full_key}', retrying {attempt+1}/{retries}...") + continue + + reply = first_reply + if reply.ok: # type: ignore + payload_bytes = bytes(reply.ok.payload) if reply.ok.payload else b"" # type: ignore + if not payload_bytes: + result = {} + else: + result = orjson.loads(payload_bytes) + return result + else: + error_payload = reply.err.payload # type: ignore + error_msg = str(error_payload) if error_payload else "unknown error" + self._logger.warning(f"Worker Error: {error_msg} retrying {attempt+1}/{retries}...") + continue + except asyncio.TimeoutError: + raise RuntimeError(f"Request to '{full_key}' timed out after {timeout} seconds") + raise RuntimeError(f"Failed to get a valid response from '{full_key}' after {retries} retries") + + async def close(self): + if self._owns_session and self._session: + self._session.close() + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + await self.close() + + def call_sync(self, *args, **kwargs): + return asyncio.run(self.call(*args, **kwargs)) + \ No newline at end of file diff --git a/utils/request/src/chongming_request/exceptions.py b/utils/request/src/chongming_request/exceptions.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/worker/README.md b/utils/worker/README.md new file mode 100644 index 0000000..e69de29 diff --git a/utils/worker/pyproject.toml b/utils/worker/pyproject.toml new file mode 100644 index 0000000..292f9e3 --- /dev/null +++ b/utils/worker/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +dependencies = [ + "eclipse-zenoh>=1.9.0", + "tomli>=2.4.1", +] +description = "chongming worker" +name = "chongming-worker" +readme = "README.md" +requires-python = ">=3.12" +version = "0.1.0" + +[tool.hatch.build.targets.wheel] +packages = ["src/chongming_worker"] diff --git a/utils/worker/src/chongming_worker/__init__.py b/utils/worker/src/chongming_worker/__init__.py new file mode 100644 index 0000000..3f2aca2 --- /dev/null +++ b/utils/worker/src/chongming_worker/__init__.py @@ -0,0 +1,40 @@ +""" +chongming_worker - 通用 Worker 框架 + +提供 Zenoh RPC 端点的装饰器定义、生命周期管理和配置管理。 +""" +from chongming_worker.decorators import ( + RPCMethod, + rpc_method, + bind_model, + auto_bind_models, + get_rpc_methods, + get_all_rpc_methods, + clear_registry, + generate_route_configs, +) +from chongming_worker.config import ( + load_worker_config, + WorkerConfig, + OperationConfig, +) +from chongming_worker.runtime import ( + create_worker, + create_workers, +) + +__all__ = [ + "RPCMethod", + "rpc_method", + "bind_model", + "auto_bind_models", + "get_rpc_methods", + "get_all_rpc_methods", + "clear_registry", + "generate_route_configs", + "load_worker_config", + "WorkerConfig", + "OperationConfig", + "create_worker", + "create_workers", +] diff --git a/utils/worker/src/chongming_worker/config.py b/utils/worker/src/chongming_worker/config.py new file mode 100644 index 0000000..240fc54 --- /dev/null +++ b/utils/worker/src/chongming_worker/config.py @@ -0,0 +1,263 @@ +import sys +from pathlib import Path +from typing import TypedDict, Literal, Optional, cast, List, Dict, Any + +# 根据Python版本选择合适的TOML解析库和Required类型 +if sys.version_info >= (3, 11): + import tomllib + from typing import Required +else: + import tomli as tomllib + from typing_extensions import Required + + +# ============================================================ +# Worker 配置类型 +# ============================================================ + +class WorkerParams(TypedDict, total=False): + """Worker 操作参数定义""" + type: str + required: bool + default: Any + description: str + + +class WorkerResponse(TypedDict, total=False): + """Worker 操作响应定义""" + type: str + required: bool + default: Any + + +class OperationConfig(TypedDict, total=False): + """ + Worker 操作配置结构 + + 对应 config/workers//*.toml 中的每个操作定义。 + """ + name: Required[str] + method: Required[Literal["GET", "POST", "PUT", "DELETE"]] + path: str + subject: Required[str] + summary: str + timeout: float + params: Dict[str, WorkerParams] + response_model: Dict[str, WorkerResponse] + + +class WorkerConfig(TypedDict, total=False): + """ + Worker 配置结构 + + 对应 config/workers//config.toml 中的顶级配置。 + """ + name: Required[str] + type: Required[str] + description: str + version: str + enabled: bool + operations: List[OperationConfig] + + +def load_worker_config(config_dir: Path) -> Dict[str, Any]: + """ + 加载 Worker 配置目录中的所有 TOML 配置文件 + + Args: + config_dir: Worker 配置目录路径,如 Path("config/workers/example") + + Returns: + 解析后的配置字典,包含所有操作的配置 + + Raises: + FileNotFoundError: 当配置目录不存在时抛出异常 + """ + if not config_dir.exists(): + raise FileNotFoundError(f"Worker 配置目录不存在: {config_dir}") + + config: Dict[str, Any] = {"operations": []} + + # 首先加载 config.toml(如果存在) + config_file = config_dir / "config.toml" + if config_file.exists(): + with open(config_file, "rb") as f: + base_config = tomllib.load(f) + config.update(base_config) + + # 然后加载所有 *.toml 中的操作定义 + for toml_file in sorted(config_dir.glob("*.toml")): + if toml_file.name == "config.toml": + continue + with open(toml_file, "rb") as f: + op_data = tomllib.load(f) + # 将文件名(不含扩展名)作为默认操作名 + op_name = toml_file.stem + if "name" not in op_data: + op_data["name"] = op_name + config["operations"].append(op_data) + + return config + + +# ============================================================ +# 旧的配置系统(与主应用 config.py 兼容) +# ============================================================ + +# 设置 env 为 development 或 production + + +class DefaultApp(TypedDict): + """应用默认配置结构""" + + name: str + version: str + description: str + debug: bool + + +class Default(TypedDict): + """系统默认配置结构,包含应用信息、环境、路径等基础配置""" + + app: DefaultApp + env: Literal["production", "development"] + prefix: str + upload_path: str + + +class SqliteConnectionArgs(TypedDict, total=False): + """SQLite数据库连接参数配置""" + + timeout: int + check_same_thread: bool + + +class DatabaseSqlite(TypedDict, total=False): + """SQLite数据库连接池及行为配置""" + + echo: bool + pool_size: int + max_overflow: int + pool_timeout: int + pool_recycle: int + pool_pre_ping: bool + connect_args: SqliteConnectionArgs + + +class Database(TypedDict, total=False): + """数据库配置结构,支持SQLite、MySQL、PostgreSQL三种数据库类型""" + + type: Required[Literal["sqlite", "mysql", "postgresql"]] + url: Required[str] + generate_schemas: bool + sqlite: DatabaseSqlite + mysql: dict + postgresql: dict + database_path: str + + +class Security(TypedDict): + """安全相关配置,包括JWT令牌和安全策略设置""" + + secret_key: str + algorithm: Literal["HS256"] + access_token_expire_minutes: int + refresh_token_expire_days: int + max_sessions_per_user: int + + +class Server(TypedDict, total=False): + """服务器运行配置,包含主机、端口、工作进程等设置""" + + host: Required[str] + port: Required[int] + reload: bool + workers: int + access_log: bool + + +class Logging(TypedDict): + """日志系统配置,定义日志级别、输出格式和文件管理策略""" + + level: str + file: str + format: str + max_size: int + backup_count: int + + +class Cors(TypedDict, total=False): + """跨域资源共享(CORS)配置""" + + allow_origins: List[str] + allow_credentials: bool + allow_methods: List[str] + allow_headers: List[str] + + +class ModuleSystem(TypedDict, total=False): + """模块系统配置,定义模块加载类型和路径""" + + type: str + path: List[str] + + +class FileSystem(TypedDict, total=False): + """文件系统配置,定义存储类型和路径""" + + type: str + path: str + + +class Env(TypedDict, total=False): + """环境特定配置结构,用于开发和生产环境的差异化配置""" + + debug: Required[bool] + server: Required[Server] + logging: Required[Logging] + cors: Cors + module_system: ModuleSystem + file_system: FileSystem + + +class Scheduler(TypedDict): + """任务调度器配置""" + + job_store_path: str + + +class Cache(TypedDict): + """缓存系统配置""" + + cache_store_path: str + + +class Config(TypedDict): + """完整配置结构,整合所有配置项,包含默认配置、数据库、调度器、缓存、安全和环境配置""" + + default: Default + database: Database + scheduler: Scheduler + cache: Cache + security: Security + development: Env + production: Env + + +def load_config(config_path: Path = Path("config.toml")) -> Config: + """ + 从TOML配置文件加载配置数据 + + Args: + config_path: 配置文件路径,默认为当前目录下的config.toml + + Returns: + Config: 解析后的配置字典对象 + + Raises: + FileNotFoundError: 当配置文件不存在时抛出异常 + """ + if not config_path.exists(): + raise FileNotFoundError(f"配置文件不存在: {config_path}") + with open(config_path, "rb") as f: + return cast(Config, tomllib.load(f)) diff --git a/utils/worker/src/chongming_worker/decorators.py b/utils/worker/src/chongming_worker/decorators.py new file mode 100644 index 0000000..1be18b1 --- /dev/null +++ b/utils/worker/src/chongming_worker/decorators.py @@ -0,0 +1,546 @@ +""" +通用 Working 装饰器框架 + +提供 @rpc_method 装饰器,用于将普通函数注册为 Zenoh RPC 端点。 +装饰器自动处理: +- 访问类型(GET/POST/PUT/DELETE) +- 接口名称(subject) +- 参数解析与类型转换 +- 响应序列化 + +兼容 gen_models 命令生成的 Pydantic 模型: +- 通过 request_model / response_model 参数绑定已生成的模型 +- 自动从插件 models 目录发现匹配的模型 +- 请求参数自动校验与响应格式自动序列化 +""" +import asyncio +import json +import time +import inspect +import importlib +from pathlib import Path +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, get_type_hints, Union + +from pydantic import BaseModel, Field, create_model, ValidationError + + +# ============================================================ +# 类型系统 +# ============================================================ + +TYPE_MAP: Dict[str, Any] = { + "float": float, + "int": int, + "str": str, + "bool": bool, + "any": Any, +} + + +def _resolve_type(annotation: Any) -> Any: + """将类型注解解析为可用的 Python 类型""" + if annotation is inspect.Parameter.empty: + return str + origin = getattr(annotation, "__origin__", None) + if origin is list: + args = getattr(annotation, "__args__", (Any,)) + return List[args[0]] if args else List[Any] # type: ignore + if origin is dict: + args = getattr(annotation, "__args__", (str, Any)) + return Dict[args[0], args[1]] if len(args) == 2 else Dict[str, Any] # type: ignore + if origin is tuple: + args = getattr(annotation, "__args__", (Any,)) + return Tuple[tuple(args)] # type: ignore + if origin is Optional: + args = getattr(annotation, "__args__", (Any,)) + return Optional[args[0]] # type: ignore + return annotation + + +def _type_to_config_str(py_type: Any) -> str: + """将 Python 类型转换为配置字符串(用于 gen_models 兼容)""" + origin = getattr(py_type, "__origin__", None) + # 处理 Union 类型(包括 Optional[X]) + if origin is Union: + args = getattr(py_type, "__args__", (Any,)) + non_none_args = [a for a in args if a is not type(None)] + if len(non_none_args) == 1: + return _type_to_config_str(non_none_args[0]) + return "str" + if origin is list: + args = getattr(py_type, "__args__", (Any,)) + return f"list[{_type_to_config_str(args[0])}]" + if origin is dict: + args = getattr(py_type, "__args__", (str, Any)) + return f"dict[{_type_to_config_str(args[0])}, {_type_to_config_str(args[1])}]" + if py_type is float: + return "float" + if py_type is int: + return "int" + if py_type is bool: + return "bool" + if py_type is str: + return "str" + return "str" + + +# ============================================================ +# 装饰器定义 +# ============================================================ + +class RPCMethod: + """ + 装饰后的 RPC 方法元数据 + + 存储原始函数、访问类型、接口名称、参数信息等, + 供 Worker 在运行时发现和调用。 + + 支持绑定 gen_models 生成的 Pydantic 模型: + - request_model: 用于请求参数的自动校验和转换 + - response_model: 用于响应数据的格式化和序列化 + """ + + def __init__( + self, + func: Callable, + subject: str, + method: str = "GET", + summary: str = "", + timeout: float = 3.0, + request_model: Optional[Type[BaseModel]] = None, + response_model: Optional[Type[BaseModel]] = None, + ): + self.func = func + self.subject = subject + self.method = method.upper() + self.summary = summary or func.__doc__ or "" + self.timeout = timeout + self.name = func.__name__ + + # 绑定的 Pydantic 模型(由 gen_models 生成,或手动传入) + self.request_model: Optional[Type[BaseModel]] = request_model + self.response_model: Optional[Type[BaseModel]] = response_model + + # 解析函数签名 + sig = inspect.signature(func) + hints = get_type_hints(func) + self.params: Dict[str, Any] = {} + self.model_param: Optional[str] = None # 如果有参数是 BaseModel,记录参数名 + self.return_type: Any = str + + for param_name, param in sig.parameters.items(): + if param_name == "self": + continue + py_type = _resolve_type(hints.get(param_name, str)) + # 检查参数类型是否是 BaseModel 子类 + if isinstance(py_type, type) and issubclass(py_type, BaseModel): + self.model_param = param_name + # 如果是 BaseModel 参数,直接用它的字段作为 params(用于配置和文档) + for field_name, field in py_type.model_fields.items(): + self.params[field_name] = _resolve_type(field.annotation) + else: + self.params[param_name] = py_type + + # 返回值类型 + self.return_type = _resolve_type(hints.get("return", Any)) + + def build_pydantic_request_model(self, model_name: str = "AutoRequest") -> Type[BaseModel]: + """根据参数注解生成 Pydantic 请求模型""" + fields: Dict[str, Any] = {} + for param_name, py_type in self.params.items(): + fields[param_name] = (py_type, Field(...)) + return create_model(model_name, **fields) + + def build_pydantic_response_model(self, model_name: str = "AutoResponse") -> Type[BaseModel]: + """根据返回值注解生成 Pydantic 响应模型""" + fields: Dict[str, Any] = {} + # 如果返回类型是 BaseModel 子类,直接使用 + if isinstance(self.return_type, type) and issubclass(self.return_type, BaseModel): + return self.return_type + # 否则包装为 {result: return_type} + fields["result"] = (self.return_type, Field(...)) + return create_model(model_name, **fields) + + def get_request_model(self) -> Optional[Type[BaseModel]]: + """ + 获取有效的请求模型,优先级: + 1. 绑定的 request_model(由用户或 gen_models 提供) + 2. 如果函数参数本身就是 Pydantic 模型(model_param),从中提取字段生成模型 + 3. 自动生成的模型(根据函数参数注解,仅 POST/PUT/PATCH) + """ + if self.request_model is not None: + return self.request_model + # 如果函数接受了 Pydantic 模型参数,用它的字段生成请求模型 + if self.model_param is not None: + sig = inspect.signature(self.func) + hints = get_type_hints(self.func) + model_type = hints.get(self.model_param) + if isinstance(model_type, type) and issubclass(model_type, BaseModel): + return model_type + # 对于 POST/PUT/PATCH,根据 params 自动生成 + if self.params and self.method in ("POST", "PUT", "PATCH"): + return self.build_pydantic_request_model(f"{self.name.title()}_Request") + return None + + def get_response_model(self) -> Optional[Type[BaseModel]]: + """ + 获取有效的响应模型,优先级: + 1. 绑定的 response_model(由用户或 gen_models 提供) + 2. 自动生成的模型(根据函数返回值注解) + """ + if self.response_model is not None: + return self.response_model + return self.build_pydantic_response_model(f"{self.name.title()}_Response") + + def validate_request(self, payload: Dict[str, Any]) -> Dict[str, Any]: + """ + 使用 Pydantic 模型校验请求参数 + + Args: + payload: 原始请求参数字典 + + Returns: + 校验通过后的参数字典(包含类型转换) + """ + req_model = self.get_request_model() + if req_model is None: + return payload + try: + validated = req_model(**payload) + return validated.model_dump() + except ValidationError as e: + raise ValueError(f"请求参数校验失败: {e.errors()}") + + def serialize_response(self, raw_result: Any) -> Any: + """ + 使用 Pydantic 模型序列化响应数据 + + Args: + raw_result: 原始函数返回值 + + Returns: + 序列化后的响应数据(dict 或列表等可 JSON 序列化的类型) + """ + resp_model = self.get_response_model() + if resp_model is None: + return raw_result + if isinstance(raw_result, dict): + try: + validated = resp_model(**raw_result) + return validated.model_dump() + except ValidationError: + # 如果无法用响应模型校验,返回原始结果 + return raw_result + if isinstance(raw_result, resp_model): + return raw_result.model_dump() + return raw_result + + async def call(self, payload: Dict[str, Any]) -> Any: + """ + 调用原始函数,自动从 payload 提取参数 + + 流程: + 1. 校验请求参数(使用绑定的 request_model 或自动生成) + 2. 提取函数需要的参数 + 3. 调用函数 + 4. 序列化响应(使用绑定的 response_model 或自动生成) + + 支持两种函数签名: + - 展开参数: def add(a: float, b: float) -> ... # 传统方式 + - Pydantic 模型: def add(req: AddGetRequest) -> ... # 工程化方式 + + Args: + payload: 请求参数字典 + + Returns: + 序列化后的响应数据 + """ + # 1. 校验请求参数 + validated_payload = self.validate_request(payload) + + # 2. 提取函数需要的参数 + kwargs = {} + + if self.model_param is not None: + # 2a. 函数参数是 Pydantic 模型 — 将整个校验后的 payload 转为模型实例传入 + req_model = self.get_request_model() + if req_model is not None: + kwargs[self.model_param] = req_model(**validated_payload) + else: + # 如果没有 request_model,从函数签名获取模型类型 + sig = inspect.signature(self.func) + hints = get_type_hints(self.func) + model_type = hints.get(self.model_param) + if isinstance(model_type, type) and issubclass(model_type, BaseModel): + kwargs[self.model_param] = model_type(**validated_payload) + else: + kwargs[self.model_param] = validated_payload + else: + # 2b. 传统方式:逐字段提取参数 + for param_name in self.params: + if param_name in validated_payload: + kwargs[param_name] = validated_payload[param_name] + + # 3. 调用函数 + if inspect.iscoroutinefunction(self.func): + result = await self.func(**kwargs) + else: + result = self.func(**kwargs) + + # 4. 序列化响应 + return self.serialize_response(result) + + +# 全局注册表:worker_type -> {subject -> RPCMethod} +_RPC_REGISTRY: Dict[str, Dict[str, RPCMethod]] = {} + + +def rpc_method( + subject: str, + method: str = "GET", + summary: str = "", + timeout: float = 3.0, + request_model: Optional[Type[BaseModel]] = None, + response_model: Optional[Type[BaseModel]] = None, +): + """ + 装饰器:将函数注册为 RPC 端点 + + 用法: + # 基本用法(自动生成模型) + @rpc_method(subject="calc.add", method="GET", summary="Add two numbers") + def add(a: float, b: float) -> float: + return a + b + + # 绑定 gen_models 生成的 Pydantic 模型 + @rpc_method( + subject="calc.add", + method="GET", + summary="Add two numbers", + response_model=AddGetResponse, + ) + def add(a: float, b: float): + return a + b + + 参数: + subject: 接口名称,格式为 "worker_type.operation" + method: HTTP 方法 (GET/POST/PUT/DELETE) + summary: 接口描述 + timeout: 超时时间(秒) + request_model: 可选的 Pydantic 请求模型(用于验证请求参数) + response_model: 可选的 Pydantic 响应模型(用于格式化响应) + """ + def decorator(func: Callable) -> Callable: + rpc = RPCMethod( + func=func, + subject=subject, + method=method, + summary=summary, + timeout=timeout, + request_model=request_model, + response_model=response_model, + ) + # 提取 worker_type(subject 的第一部分) + worker_type = subject.split(".")[0] if "." in subject else "default" + if worker_type not in _RPC_REGISTRY: + _RPC_REGISTRY[worker_type] = {} + # 使用 subject + "#" + method 作为唯一 key,允许多个 HTTP 方法共享同一 subject + registry_key = f"{subject}#{method.upper()}" + _RPC_REGISTRY[worker_type][registry_key] = rpc + + # 给函数附加元数据,方便外部访问 + func._rpc_meta = rpc # type: ignore + return func + + return decorator + + +def bind_model( + subject: str = "", + method: str = "", + request_model: Optional[Type[BaseModel]] = None, + response_model: Optional[Type[BaseModel]] = None, +): + """ + 装饰器:为已注册的 RPC 端点绑定 Pydantic 模型 + + 用于 gen_models 生成模型后,将模型绑定到用 @rpc_method 定义的端点。 + 无需修改原有的 @rpc_method 调用。 + + 用法: + from .models.pydantic import AddGetResponse + + @bind_model(subject="example.add", method="GET", response_model=AddGetResponse) + def add(): + pass # 函数体不重要,仅用于匹配已注册的端点 + + 参数: + subject: 要绑定的接口名称(与 @rpc_method 中的 subject 一致) + method: 要绑定的 HTTP 方法 + request_model: Pydantic 请求模型 + response_model: Pydantic 响应模型 + """ + def decorator(func: Callable) -> Callable: + nonlocal subject, method + if not subject and hasattr(func, '_rpc_meta'): + subject = func._rpc_meta.subject # type: ignore[attr-defined] + method = func._rpc_meta.method # type: ignore[attr-defined] + + worker_type = subject.split(".")[0] if "." in subject else "default" + method_upper = method.upper() + registry = _RPC_REGISTRY.get(worker_type, {}) + # 用新 key 格式查找: subject#METHOD + rpc = registry.get(f"{subject}#{method_upper}") + + if rpc is None: + raise ValueError( + f"未找到已注册的 RPC 端点: subject='{subject}', method='{method}'. " + f"请确保 @rpc_method 装饰器已先于 @bind_model 执行。" + ) + + # 如果指定了 method,进一步筛选 + if method and rpc.method != method.upper(): + return func + + if request_model is not None: + rpc.request_model = request_model + if response_model is not None: + rpc.response_model = response_model + + func._rpc_meta = rpc # type: ignore[attr-defined] + return func + + return decorator + + +def auto_bind_models(plugin_name: str): + """ + 自动从插件 models 目录发现并绑定 Pydantic 模型 + + 扫描 src/plugins//models/pydantic.py 中定义的所有模型, + 尝试按命名约定匹配已注册的 RPC 端点。 + + 命名约定: + - 请求模型: Request (e.g., AddPostRequest) + - 响应模型: Response (e.g., AddGetResponse) + + 用法: + # 在插件 __init__.py 中调用 + from ...decorators import auto_bind_models + auto_bind_models("example") + """ + try: + models_module = importlib.import_module(f"plugins.{plugin_name}.models.pydantic") + except ImportError: + try: + models_module = importlib.import_module(f"src.plugins.{plugin_name}.models.pydantic") + except ImportError: + print(f"⚠️ auto_bind_models: 未找到 models 模块 (plugins.{plugin_name}.models.pydantic)") + return + + # 获取模块中所有 BaseModel 子类 + model_classes = {} + for name in dir(models_module): + obj = getattr(models_module, name) + if isinstance(obj, type) and issubclass(obj, BaseModel) and obj is not BaseModel: + model_classes[name] = obj + + if not model_classes: + print(f"⚠️ auto_bind_models: 未找到任何 Pydantic 模型") + return + + registry = _RPC_REGISTRY.get(plugin_name, {}) + bound_count = 0 + + for _key, rpc in registry.items(): + op_name = rpc.subject.split(".")[-1] if "." in rpc.subject else rpc.subject + op_camel = op_name.title().replace("-", "").replace("_", "") + method_tag = rpc.method.title() + + # 尝试匹配请求模型: Request + req_model_name = f"{op_camel}{method_tag}Request" + if req_model_name in model_classes: + rpc.request_model = model_classes[req_model_name] + bound_count += 1 + print(f" ✓ 绑定请求模型: {req_model_name} -> {rpc.subject} [{rpc.method}]") + + # 尝试匹配响应模型: Response + resp_model_name = f"{op_camel}{method_tag}Response" + if resp_model_name in model_classes: + rpc.response_model = model_classes[resp_model_name] + bound_count += 1 + print(f" ✓ 绑定响应模型: {resp_model_name} -> {rpc.subject} [{rpc.method}]") + + if bound_count > 0: + print(f"✅ auto_bind_models: 已绑定 {bound_count} 个模型到 {plugin_name} 的 RPC 端点") + else: + print(f"⚠️ auto_bind_models: 未找到匹配的模型绑定") + + +def get_rpc_methods(worker_type: str) -> Dict[str, RPCMethod]: + """获取指定 worker 类型的所有 RPC 方法""" + return _RPC_REGISTRY.get(worker_type, {}) + + +def get_all_rpc_methods() -> Dict[str, Dict[str, RPCMethod]]: + """获取所有已注册的 RPC 方法""" + return _RPC_REGISTRY + + +def clear_registry(): + """清空注册表(主要用于测试)""" + _RPC_REGISTRY.clear() + + +# ============================================================ +# 路由配置生成器(从装饰器元数据生成 TOML 兼容的配置) +# ============================================================ + +def generate_route_configs(worker_type: str) -> List[Dict[str, Any]]: + """ + 从装饰器注册表生成路由配置列表 + + 返回的配置与 config/workers//*.toml 格式兼容, + 可用于动态路由注册或生成配置文件。 + """ + methods = get_rpc_methods(worker_type) + configs: List[Dict[str, Any]] = [] + for _key, rpc in methods.items(): + # 从 subject 提取操作名(最后一部分) + op_name = rpc.subject.split(".")[-1] if "." in rpc.subject else rpc.subject + configs.append({ + "subject": rpc.subject, + "path": f"/{op_name}", + "method": rpc.method, + "params": [f"{name}: {_type_to_config_str(pt)}" for name, pt in rpc.params.items()], + "response_model": _build_response_spec(rpc), + "summary": rpc.summary, + "timeout": rpc.timeout, + }) + return configs + + +def _build_response_spec(rpc: RPCMethod) -> Dict[str, Any]: + """从 RPC 方法构建 response_model 配置""" + spec: Dict[str, Any] = {} + + # 优先使用绑定的 response_model + resp_model = rpc.response_model + if resp_model is None: + resp_model = rpc.build_pydantic_response_model() + + if isinstance(resp_model, type) and issubclass(resp_model, BaseModel): + # 遍历 Pydantic 模型字段 + for field_name, field in resp_model.model_fields.items(): + field_type = _type_to_config_str(field.annotation) + required = field.is_required() + default = field.default if not field.is_required() else None + spec[field_name] = { + "type": field_type, + "required": required, + } + if default is not None: + spec[field_name]["default"] = default + else: + spec["result"] = {"type": _type_to_config_str(rpc.return_type), "required": True} + return spec diff --git a/utils/worker/src/chongming_worker/runtime.py b/utils/worker/src/chongming_worker/runtime.py new file mode 100644 index 0000000..284fa75 --- /dev/null +++ b/utils/worker/src/chongming_worker/runtime.py @@ -0,0 +1,179 @@ +""" +Worker 运行时 — 通用的生命周期管理 + +提供 create_worker / create_workers 工厂函数,供所有 Worker 插件共享。 +""" +import os +import asyncio +import json +import orjson +from typing import Any, Dict, List, Optional + +import zenoh + +from chongming_worker.decorators import ( + RPCMethod, + get_rpc_methods, +) + +async def create_worker( + session: zenoh.Session, + worker_type: str, + worker_id: str, + config: Optional[Dict[str, Any]] = None, + worker_manager=None, +): + """ + 创建一个 Worker 协程 + + 自动发现通过 @rpc_method 注册的端点,为每个 subject 声明 Zenoh Queryable, + 收到请求后按 _method 字段分派到具体方法。 + + Args: + session: Zenoh 会话 + worker_type: Worker 类型标识(匹配 subject 的第一部分) + worker_id: Worker 唯一 ID + config: 自定义配置参数 + worker_manager: WorkerManager 实例(用于本地注册) + """ + print(f"[Worker] {worker_id} 启动 (type={worker_type})") + + # 1. 注册存活令牌 + token = session.liveliness().declare_token(f"worker/{worker_type}/{worker_id}") + + # 2. 注册到本地 WorkerManager(若有) + if worker_manager is not None: + worker_manager.register_worker(worker_type, worker_id) + + # 3. 获取该 worker 类型的所有 RPC 方法 + rpc_methods = get_rpc_methods(worker_type) + if not rpc_methods: + print(f"[Worker] {worker_id} 警告: 未找到任何 RPC 方法") + else: + print(f"[Worker] {worker_id} 发现 {len(rpc_methods)} 个端点:") + for _key, rpc in rpc_methods.items(): + print(f" {rpc.method} {rpc.subject} -> {rpc.name}({', '.join(rpc.params.keys())})") + + # 4. 按 subject 分组(多个方法可共享同一 subject,如 GET + POST) + subject_methods: Dict[str, List[RPCMethod]] = {} + for registry_key, rpc in rpc_methods.items(): + subject_methods.setdefault(rpc.subject, []).append(rpc) + + # 5. 为每个 subject 创建回调函数并声明 Queryable + stop_event = asyncio.Event() + + def make_callback(subject: str, methods: List[RPCMethod]): + """创建处理特定 subject 的回调函数""" + def callback(query: zenoh.Query): + try: + # 解析请求负载 + # payload_str = str(query.payload) if query.payload else "{}" + payload_bytes = bytes(query.payload) if query.payload else b"" + if not payload_bytes: + data = {} + else: + data = orjson.loads(payload_bytes) + http_method = data.get("_method", "GET").upper() + + # 匹配方法(按 HTTP 方法) + matched = next((m for m in methods if m.method.upper() == http_method), None) + if not matched and methods: + matched = methods[0] # 降级到第一个 + if not matched: + query.reply( + key_expr=query.key_expr, + payload=json.dumps({"error": f"No method for {subject}"}).encode("utf-8") + ) + return + + # 调用 RPC 方法(支持同步和异步) + result = asyncio.run(matched.call(data)) + + # 回复 + query.reply( + key_expr=query.key_expr, + payload=orjson.dumps(result) + ) + print(f"[Worker] {worker_id} 回复 {matched.method} {subject}: {result}") + except Exception as e: + error_response = { + "result": 0.0, + "operation": "error", + "error": str(e), + "worker": worker_id + } + query.reply( + key_expr=query.key_expr, + payload=json.dumps(error_response).encode("utf-8") + ) + print(f"[Worker] {worker_id} 异常 {subject}: {e}") + return callback + + # 声明所有 Queryable + queryables: List[zenoh.Queryable] = [] + for subject, methods in subject_methods.items(): + key = f"worker/{worker_type}/{worker_id}/{subject}" + # 使用回调方式声明 + q = session.declare_queryable(key, make_callback(subject, methods)) + queryables.append(q) + print(f"[Worker] {worker_id} Queryable: '{key}' [{', '.join(m.method for m in methods)}]") + + # 6. 保持 worker 存活直到被取消 + try: + await stop_event.wait() + except asyncio.CancelledError: + print(f"[Worker] {worker_id} 正在关闭...") + except Exception as e: + print(f"[Worker] {worker_id} 发生异常: {e}") + finally: + # 清理 + if worker_manager is not None: + worker_manager.unregister_worker(worker_type, worker_id) + token.undeclare() + for q in queryables: + q.undeclare() + print(f"[Worker] {worker_id} 已关闭") + +def create_workers( + session: zenoh.Session, + config: dict, + count: int = 1, + worker_manager=None, +) -> list[asyncio.Task]: + """ + 创建一组 Worker 协程任务 + + 工厂函数,供 WorkerManager 动态加载使用。 + + Args: + session: Zenoh 会话 + config: Worker 配置字典(至少需要 "type" 字段) + count: 启动的 Worker 副本数 + worker_manager: WorkerManager 实例(可选) + + Returns: + list[asyncio.Task]: Worker 协程任务列表 + """ + worker_type = config.get("type", "default") + pid = os.getpid() + tasks = [] + for i in range(count): + worker_id = f"worker-{i}-{pid}" + task = asyncio.create_task( + create_worker( + session=session, + worker_type=worker_type, + worker_id=worker_id, + config=config, + worker_manager=worker_manager, + ) + ) + tasks.append(task) + print(f"[Worker] 已创建任务: {worker_id} (type={worker_type})") + return tasks + + +__all__ = [ + "create_worker", + "create_workers", +] diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..e85a5f9 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1297 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[manifest] +members = [ + "chongming-request", + "chongming-worker", + "chongming-zenoh", +] + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, +] + +[[package]] +name = "apscheduler" +version = "3.11.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "tzlocal" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/6b/eeff360196bb20b312c9e762a820fd1b2c6d809466c755ef57863478e454/apscheduler-3.11.3.tar.gz", hash = "sha256:cd2fcc9330039a81a5893472ad49facf23a6d5604cbe1d918c835c6de7834d5a", size = 110312, upload-time = "2026-06-28T19:39:22.493Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/c9/8638db32514dbb9157b3d82680c6faea89283523edf9ed2415ea3884f2ae/apscheduler-3.11.3-py3-none-any.whl", hash = "sha256:bbeb2ec02d23d3c06a6c07ed7f0f3939ada6680eb121fae809a69bb42c537a30", size = 66024, upload-time = "2026-06-28T19:39:20.982Z" }, +] + +[[package]] +name = "boto3" +version = "1.43.48" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/bc/6b285efc5a7ebb27762f20d3fa2d78b8ff86d069d8eef343876d03466273/boto3-1.43.48.tar.gz", hash = "sha256:0077b3f38d47034f53ec68f3976f9d9beeabcc0faee7b1d6fa6b666e0852eca7", size = 112674, upload-time = "2026-07-14T19:29:57.742Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/ec/3e4bff3d696121631174471505a7669d9d22c183991b4dcc245101603cec/boto3-1.43.48-py3-none-any.whl", hash = "sha256:5866f8a99877342db130e7236a8048b63d3d8e74c46d4ef101771350019bd3df", size = 140032, upload-time = "2026-07-14T19:29:55.819Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.48" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/69/76c5576654c0982351a821c544042ccd7a25a060776453a3c15e0486e47e/botocore-1.43.48.tar.gz", hash = "sha256:4f60c1072fb529610359cdb6df92c59615d739df3b0a124091d3bbd28f7ea00c", size = 15700978, upload-time = "2026-07-14T19:29:45.916Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/b0/28d40443ed912260d55cf7a5d377378fc43e384627fd57b632472cbea7bf/botocore-1.43.48-py3-none-any.whl", hash = "sha256:cc9b4e925c1913d662194507f4f9b37aa744dcd2361af31c013672eafe824490", size = 15385380, upload-time = "2026-07-14T19:29:41.826Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, +] + +[[package]] +name = "chongming-request" +version = "0.1.0" +source = { editable = "utils/request" } +dependencies = [ + { name = "eclipse-zenoh" }, +] + +[package.metadata] +requires-dist = [{ name = "eclipse-zenoh", specifier = ">=1.9.0" }] + +[[package]] +name = "chongming-worker" +version = "0.1.0" +source = { editable = "utils/worker" } +dependencies = [ + { name = "eclipse-zenoh" }, + { name = "tomli" }, +] + +[package.metadata] +requires-dist = [ + { name = "eclipse-zenoh", specifier = ">=1.9.0" }, + { name = "tomli", specifier = ">=2.4.1" }, +] + +[[package]] +name = "chongming-zenoh" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "apscheduler" }, + { name = "boto3" }, + { name = "chongming-request" }, + { name = "chongming-worker" }, + { name = "diskcache" }, + { name = "eclipse-zenoh" }, + { name = "fastapi" }, + { name = "gunicorn" }, + { name = "modelvault" }, + { name = "module-bank" }, + { name = "nanio" }, + { name = "orjson" }, + { name = "python-multipart" }, + { name = "sqlite-vfs" }, + { name = "tomli" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] + +[package.dev-dependencies] +dev = [ + { name = "nuitka" }, + { name = "pyarmor" }, + { name = "pyinstaller" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "apscheduler" }, + { name = "boto3", specifier = ">=1.43.48" }, + { name = "chongming-request", editable = "utils/request" }, + { name = "chongming-worker", editable = "utils/worker" }, + { name = "diskcache" }, + { name = "eclipse-zenoh" }, + { name = "fastapi", extras = ["uvicorn"] }, + { name = "gunicorn" }, + { name = "modelvault", specifier = ">=0.16.0" }, + { name = "module-bank" }, + { name = "nanio", specifier = ">=0.1.5" }, + { name = "orjson", specifier = ">=3.11.9" }, + { name = "python-multipart", specifier = ">=0.0.32" }, + { name = "sqlite-vfs" }, + { name = "tomli" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "nuitka" }, + { name = "pyarmor" }, + { name = "pyinstaller" }, + { name = "ruff" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "49.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc" } +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" }, +] + +[[package]] +name = "eclipse-zenoh" +version = "1.9.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/42/c8502d0e77f74b9cf4c192a01e620b3d15273d371464485796807d202d9d/eclipse_zenoh-1.9.0.tar.gz", hash = "sha256:b0477ab431132ebfe1096eccac13ea0066d50d1528d726c8872c00e0345070d1", size = 164557, upload-time = "2026-04-10T13:23:35.883Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/3b/22b9104b0a022bd2b1627b4866876831585eda2eacb9ca1f3b4b8e847945/eclipse_zenoh-1.9.0-cp39-abi3-linux_armv6l.whl", hash = "sha256:15b6f37c407617ea4de32d32835cbcab4d1a116b892477490fc6c10a7d27c73b", size = 10664168, upload-time = "2026-04-10T13:23:15.008Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/c5/ee0815c7ec49c5a29307cd935478305159bb3f0b2489f8c54fc6db3fdf36/eclipse_zenoh-1.9.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6f66059b12e1ec53c70bc25192b0e74502751759064726dbb153ed6dd8f4dc8b", size = 19942168, upload-time = "2026-04-10T13:23:17.785Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/6a/42b83b4e8c262ebbb3bcae702394478326c807f54b3162130b0a603e1a01/eclipse_zenoh-1.9.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:180dd2a6da3b86b52e87f5e470a1f8a86db03c519978b22ffb1dc7c11f98ef3b", size = 10225694, upload-time = "2026-04-10T13:23:20.244Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/57/28e66893801b63df36fea355a64b6fc22637e1148a952ee11e3039ae955e/eclipse_zenoh-1.9.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:949d82851bc9e3ad646fd1307ee544ed23359dcfd18d4065075fc592f6ab6fa7", size = 10517069, upload-time = "2026-04-10T13:23:23.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/2f/be614f1f7f4e046da2764cd36227d19db3655839219744ce7a12e6e2dae6/eclipse_zenoh-1.9.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a1fe847225cda21e3e74677cfd4ddfd2e72600d5a56968d4229d981c67f78d4", size = 11580068, upload-time = "2026-04-10T13:23:25.594Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/1b/2a074d4f4595bd37c3d12f1b2ad49bceef5c8cd0962cbfd97d1d39f32e1f/eclipse_zenoh-1.9.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43299593891cfd648bca4b2aa00f3dca916508a49a0c9e6960902e6e867b247e", size = 10537556, upload-time = "2026-04-10T13:23:28.414Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/33/c3116f1bf7647ee0ea8972efbe0fe5710ae75ea7226440a8fda7f04a4cbc/eclipse_zenoh-1.9.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8c139a43706c8ff3c94fa625008af8667687c161a8395ad1fa3faff29c16fae4", size = 10721249, upload-time = "2026-04-10T13:23:30.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/16/a94c4f37e3a088faadf4b5fbc64e5f69dea1023dc7efc49b3be0e0ecc953/eclipse_zenoh-1.9.0-cp39-abi3-win_amd64.whl", hash = "sha256:5dfb352eca4585b85edbbc84c6db58906008e202823ca280496c0b867f9719f0", size = 9124510, upload-time = "2026-04-10T13:23:34.119Z" }, +] + +[[package]] +name = "fastapi" +version = "0.139.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/af/a5f50ccfa659ec1802cb4ca842c23f06d906a8cc9aef6016a2caeea3d4ed/fastapi-0.139.0.tar.gz", hash = "sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145", size = 423016, upload-time = "2026-07-01T16:35:33.436Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/7c/8e3c6ad324ea5cb36604fc3f968554887891c316d9dfde57761611d907ad/fastapi-0.139.0-py3-none-any.whl", hash = "sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189", size = 130339, upload-time = "2026-07-01T16:35:32.19Z" }, +] + +[[package]] +name = "gunicorn" +version = "26.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/b7/a4a3f632f823e432ce6bc65f62961b7980c898c77f075a2f7118cb3846fe/gunicorn-26.0.0.tar.gz", hash = "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf", size = 727286, upload-time = "2026-05-05T06:38:25.529Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/40/9c2384fc2be4ad25dd4a49decd5ad9ea5a3639814c11bd40ab77cb9f0a14/gunicorn-26.0.0-py3-none-any.whl", hash = "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", size = 212009, upload-time = "2026-05-05T06:38:23.007Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, +] + +[[package]] +name = "modelvault" +version = "0.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/c8/e2d64e9d80040efd1e01fe25ef121f5faa473ce40631c34dbe4e66c1f271/modelvault-0.16.0.tar.gz", hash = "sha256:aa8378dbb9f6beffa76ae8cbbfacf549486d0e195a72e28ca299b737cc839eef", size = 236231, upload-time = "2026-06-08T01:04:07.736Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/21/2ce73f22e30413f9167324e5e61bcad4dd7a50b038c2b08c3aa0150774e8/modelvault-0.16.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:a0a33a00387d2bf41c8aa69532a4669275614ba758decd502f5d6ec0f417442d", size = 2314107, upload-time = "2026-06-08T01:03:57.258Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/ec/2a83eca634b8576494a9aa795d8da6b204003ebf17e63d44f33ef368c4e0/modelvault-0.16.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec12950aa612690991692d7973e74f4e15bbb3f8d67e92d11532eb436d42f39d", size = 2207973, upload-time = "2026-06-08T01:03:33.756Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/44/27fcfc8ac0536e0a20cc8bb9ee01a87063b2ff2cef43d916f48bfa171f4f/modelvault-0.16.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1245e57151e7508606fd34e4d8510a460c1f3810157c09a4449936cf3a798bde", size = 2442060, upload-time = "2026-06-08T01:04:06.264Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/a3/0042eb943d36b449d45b2d617ec02db11459328f4111a48683c2965331dd/modelvault-0.16.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:55f1e962e785da4c896b15476897f81b300d7147f6d5b51f6666497c5d8d3674", size = 2481291, upload-time = "2026-06-08T01:04:08.08Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/e9/96e0a637d080419b221e63a4ee393d18aaef3b9132bcecfaa528b5c6f55f/modelvault-0.16.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5cafa8505f9c0e4d15eaa43e320430c688551968bbe8cfe7510d2c9f63afab68", size = 1957214, upload-time = "2026-06-08T01:03:52.779Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/ed/a8c0b32454e5a315e55ad3f3b70671b9f66495915d658419aa8484c46cd7/modelvault-0.16.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b2b65d4017f83675f9c1a8c0c9b9e8e1ea541526776e57c6e698e81f677b4a2", size = 2075934, upload-time = "2026-06-08T01:04:01.389Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/29/e250a6edd613451c680dbf20ddcbe528c68762d369f0f5bf3ac31090796a/modelvault-0.16.0-cp39-abi3-win_amd64.whl", hash = "sha256:95e42e53aba0de5c8f798863ab25a3271ef8405c51728a15399977e39a187a66", size = 2091287, upload-time = "2026-06-08T01:05:47.349Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/44/352248e4a1723994dd3b6ccd1306b74b3c6c2bd5b8e02c66572626e5ae66/modelvault-0.16.0-cp39-abi3-win_arm64.whl", hash = "sha256:7b3defb3ce620039e63eef90bb7973d727b42ab8e3bc8303490429f9bc32c96b", size = 1894605, upload-time = "2026-06-08T01:05:34.93Z" }, +] + +[[package]] +name = "module-bank" +version = "0.3.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyarmor" }, +] +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" }, +] + +[[package]] +name = "nanio" +version = "0.1.5" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "defusedxml" }, + { name = "starlette" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/3d/e98f7433e12451a772cf47a591e5488ee36545d1c2024c0bf6bb47af8300/nanio-0.1.5.tar.gz", hash = "sha256:e2adbb4091abd0950a232a3413c8e738fe2c39cde6993b153ce0f6667e11a1d6", size = 53996, upload-time = "2026-04-09T18:17:21.362Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e0/6d/31b4c23be731bb2046cf855743fbcab08909b1ce3b099c6219e481693319/nanio-0.1.5-py3-none-any.whl", hash = "sha256:a3a083c5b6ab1b945488d81e80342eba0fa6385f1882bfcde205faf11a435749", size = 67772, upload-time = "2026-04-09T18:17:19.97Z" }, +] + +[[package]] +name = "nuitka" +version = "4.1.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/d8/bdb7febea4b4fe5d3d6fe2610f946771f03e792e05a5e8ec00b62c00b265/nuitka-4.1.3.tar.gz", hash = "sha256:838ff8899dc2f0b652d4fcf6c5d7466cb7ad5abcb005668ac622d1e40f4d8a8d", size = 4565475, upload-time = "2026-06-21T10:48:04.369Z" } + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, +] + +[[package]] +name = "pyarmor" +version = "9.2.5" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "pyarmor-cli-core" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/16/42a6ddc287cd228fae6c2cf6590f579406efed7c85ac508bb6c5f940f073/pyarmor-9.2.5.tar.gz", hash = "sha256:6f2253282d9e502e583d18413c8fb666f6c5b94e5da939a7a13cd834b639ed96", size = 3012802, upload-time = "2026-05-20T15:29:57.345Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/a2/50e7e8fdb78bc6f6e56c3d91703c644b3bf92fb0d9860a41224206291bd2/pyarmor-9.2.5-2-py3-none-any.whl", hash = "sha256:bae45b0ca633c0dcc46a95138c1789441e4bb313c9c0c914e8ee2f15fa2f2ff3", size = 3072971, upload-time = "2026-06-01T15:12:34.009Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/16/af84ca6cd432f84bd94eacbef3f531d86f4bcdf6ce3583fa0267568a4e4f/pyarmor-9.2.5-py3-none-any.whl", hash = "sha256:bad7d878396c654a9e9fb0103ede00784b9bafdf9df893152f4b29bfce7c564c", size = 3072982, upload-time = "2026-05-20T15:30:03.938Z" }, +] + +[[package]] +name = "pyarmor-cli-core" +version = "8.1.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/b4/c0b6f09ea1dc87c00839908d31fd902075496feb319ffd1b03b665a65477/pyarmor_cli_core-8.1.1-2-cp312-cp312-win32.whl", hash = "sha256:790f0957124d44d1527edf7a677ffc006cddc827998e4c7981f3b20fd3db14a9", size = 3743797, upload-time = "2026-05-20T09:27:01.65Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/0d/0bdccf84af431405b1e52d5a8cd6e4bf37a84e04520276da372d0ae2b577/pyarmor_cli_core-8.1.1-2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:f1fe8ec72cd1a1aeffcb39b6323f2ae3e5a2ba45d02bc67ef938e46bc7e207bc", size = 522710, upload-time = "2026-05-20T15:25:57.965Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/6c/6f6bc236208a4e3577ccf8b4849b520234294f804a33a77f0b91ead0027e/pyarmor_cli_core-8.1.1-2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6d22b879c1a2fa5a81de4de504e244c808e49e7911f95aed6d3329e38f8326ec", size = 414409, upload-time = "2026-05-20T15:26:04.24Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/e9/e9f8a52202ee74b9eec6a318f0fb8ed9849c7dbf47573a05d891f39f2476/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux1_i686.whl", hash = "sha256:8b481c807197f3cd2a15857adf2937cec5ae5a178c6008225b9ff94b83ff5ccb", size = 495375, upload-time = "2026-05-20T15:26:13.74Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/97/9e70a68220e190a886b74dabd080f6b8995266e2130ec79d28f6f6e31d20/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:5b5fd2301e628873be9f3ec32788d52e32625c414bc8fb5f4e93136e64a3f58d", size = 462070, upload-time = "2026-05-20T15:26:18.961Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/1a/07859e23cfa07d249da1bff11ee9beda8431f5a978d1a16256c325fe592c/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:6187ca1d4b2260332f74e5e60a4c9b0fc178dee9e69155c5cbb4befe3babccec", size = 609497, upload-time = "2026-05-20T15:26:24.233Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/0f/1a074362b19dd18a3f4ea27910cb1d08589609e4ec1ef9c181999bec015a/pyarmor_cli_core-8.1.1-2-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:a2c0d67975d669a7045c809299a6dcdab305541948c24955fad737f5349bf26f", size = 1004965, upload-time = "2026-05-20T15:26:31.519Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/98/70b210c8bb2cf27c12eed161c6441f753aa68b0739a177f5bbf5a002be20/pyarmor_cli_core-8.1.1-2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3c6c748f98231dcdb75bcce9cf6b8fe86d61674c4a44ba49c12c4a8c478348cf", size = 655390, upload-time = "2026-05-20T15:26:38.625Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/e0/10b373cbb4fe1b140adb804b156fa7c7483f3c24e76a283cfb4047361b1f/pyarmor_cli_core-8.1.1-2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:20162349d62ec09d22299c9bedfe0b13e22c2d27757cb53c523eb950adcd7140", size = 450993, upload-time = "2026-05-20T15:26:44.049Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/97/f1a395a177ab833d560fcf56d3f97625a708669cdc567b46abe286c64ce8/pyarmor_cli_core-8.1.1-2-cp314-cp314-win32.whl", hash = "sha256:999a25c17a09af3e9481a9b2bc3f011526ef3333077cfa703a8014f899d528eb", size = 465426, upload-time = "2026-05-20T15:26:52.662Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/58/44112ea2938c023f8f94335deaae40c53a925cd5cb37386cbb904e4a97a5/pyarmor_cli_core-8.1.1-2-cp314-cp314-win_amd64.whl", hash = "sha256:add3f37e0fe78ca4678788c4c4042761aa9db7948a4395ccf9db9091fc7e07c6", size = 424587, upload-time = "2026-05-20T15:26:57.955Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/b4/48b1b05625ed48f2d786ea0c50a9b446b0d105c554a85e9eccb81bd98ec0/pyarmor_cli_core-8.1.1-2-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:119a0be4d75fc6c4f1c36a7beeee74a55819a86f4cedf3f0cab7afaf7f509d3a", size = 522845, upload-time = "2026-05-20T15:27:04.646Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/04/a09dd4b72dca9e43e02597aabf6213f27c2f12fafb477992fe8276a82d7b/pyarmor_cli_core-8.1.1-2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e2b656a703146507d5d800c4d952a656c9f4d68a4dd96a0c39eca28b63642914", size = 414405, upload-time = "2026-05-20T15:27:16.212Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/91/7e3a773c6e1756682db9c11783dd0e4a0894b9642771118ae2a4d1c436db/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux1_i686.whl", hash = "sha256:e0210f89acae597c574a5a58290b20feb94952dc6c002f8e280cc1b372db0189", size = 495327, upload-time = "2026-05-20T15:27:21.122Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/4d/fd2e36ecf479c9fe57b52c19c3f6bca0913c85fa980447af29a2ba9339ba/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:1675d2c7e157dbeb096fab72d8c06c5fda0ff68e468a28d3d9624aaaf90ff237", size = 461865, upload-time = "2026-05-20T15:27:27.146Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/8c/967d1ac0908d8c2c9ea73fbb2cb8ba31e4ec4fc290b7e3532ca0400d9115/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:bbe9429f19c4d6e70cec183cc78abb2f7ed483789fc17af6059871149d722e62", size = 609349, upload-time = "2026-05-20T15:27:33.218Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/50/2b2f5db7f6c28c22a2a67e1483dfda65e2285a94456dcbc6122fe9402849/pyarmor_cli_core-8.1.1-2-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:5396c158a32cc6e2dc9ae317d819bb7c28ac83d832014e2db3e062679380ce35", size = 1004843, upload-time = "2026-05-20T15:27:40.199Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/c6/ff8d539dddc7e28a8f987a16fa85b9c27b142b64e54b644461f1e1bd8ed1/pyarmor_cli_core-8.1.1-2-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:5625035d8026ad905701f8c9c56140357776112cdd533ac614d3427c3084fbe8", size = 655278, upload-time = "2026-05-20T15:27:46.939Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/52/a8586c25e8ba3ec9c69556d6bc153097b8e065f87753e40bec9e1bea7f09/pyarmor_cli_core-8.1.1-2-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:a6ebbcbde0d650ef5f11e8c614dabde12636c7277c22ea9176f03aa8733c2cdb", size = 450928, upload-time = "2026-05-20T15:27:53.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/fb/d9df3bdce9c7480df3d91139539fd27332db3f5a984551201db0189dfdd3/pyarmor_cli_core-8.1.1-2-cp315-cp315-win32.whl", hash = "sha256:4fdd33615b75302fd48a0851a3f7ace1f4a4f515cb09edce5fd0d5d4e3919bf1", size = 465363, upload-time = "2026-05-20T15:27:58.894Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/e7/39a4c74668fdb7ad536264aa73d0439a2d98f816452e1955c2cce36b65bf/pyarmor_cli_core-8.1.1-2-cp315-cp315-win_amd64.whl", hash = "sha256:3c430c33fde5f36305c6e90356d7136da532166b20b9ad69bb5aefdbaea2e9cc", size = 424487, upload-time = "2026-05-20T15:28:06.382Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/b4/33fd5192cedc4600e9de8c9c7519205f421840da1b0df6e9491afca0a072/pyarmor_cli_core-8.1.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:4a6e09545fe74dc304992a0b002cc8d426c16ac3670d6f602d51838f080a2f31", size = 515480, upload-time = "2026-04-09T01:27:47.282Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/e5/3310cb2e29bab80bd923d7e0749c2163550effe1d1a6c6d5d95f5c58d183/pyarmor_cli_core-8.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:df035854a6603d4861a50156484027a03c01a7b9385b5365cabea4871ac72e3e", size = 407703, upload-time = "2026-04-09T01:27:51.869Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/66/1aa494d360a05fd90677db8c4b8385dedd2083fe96fe6607ea555f800c87/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux1_i686.whl", hash = "sha256:ec304457ff31b4939c8527640d7f525e78e9c91cc8307a11f068ef1291710b40", size = 489280, upload-time = "2026-04-09T01:27:56.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/ad/5f246234ce778dd3b7bd4faa2bded71690bbcddbf8f29506cba131ae4f85/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:9b65a62a71a88fb7534002067b972aac2f363e871ad34563579550fec1a260cb", size = 457685, upload-time = "2026-04-09T01:28:01.313Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/5a/cbaa2088483837ded4bb6301993de16743c633f230bf29947cdc10f7c307/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:d1c4e623dbb8bd93fc4273a7ca79d192618ca36cf74aceaeca9d36c83608ed77", size = 604340, upload-time = "2026-04-09T01:28:06.227Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/87/39/2686a96a3d80c5ef61fea10066677ddf3678bdd48afcec04a0d268203fa0/pyarmor_cli_core-8.1.1-cp312-cp312-manylinux2014_armv7l.whl", hash = "sha256:0c10ad18ca2cb285b4b6c2bfdba6bc0234e84272f52bf692dce25ae7c6a21d56", size = 1001029, upload-time = "2026-04-09T01:28:11.426Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/e9/da903ea517387ac3d560b324fd03a7f50267cc2f68e99f84934fd9ee3ef9/pyarmor_cli_core-8.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:812201887aa2fb90f8b865a5dd443aa841cb951bd25d14dad843ce11b3ceef57", size = 649535, upload-time = "2026-04-09T01:28:16.373Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/eb/fcd742456b3a475e16eb5c3468132a9950095169cf1e6a64e4fb4bac208d/pyarmor_cli_core-8.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88e129603512326ce1440e3aa105506304424ee40366cc19f0a72a9aeeabecf7", size = 446443, upload-time = "2026-04-09T01:28:21.054Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/87/b58aed090a6a9debf65045f37e93b779e330c22106aa4cbb9aaaf7bb12b8/pyarmor_cli_core-8.1.1-cp312-cp312-win32.whl", hash = "sha256:b645d69c88f55a08d34fdd547759079ac1722e246d8e994ce3eb07937bdf318b", size = 460155, upload-time = "2026-04-09T01:28:25.665Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/b8/1dc8b15bc1607a27ed78025d5f50c97c78542930cab15a6d6d457b664bb3/pyarmor_cli_core-8.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:09df045973b5de85ecb950f3fffc23686caa5b43749d4d966ccd150719639e2b", size = 420620, upload-time = "2026-04-09T01:28:30.36Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/35/4b2969ebaea72869a5ba13847d47682ec77a1ff894fc83450e23e7257845/pyarmor_cli_core-8.1.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:86a576252f26752cfed5a1f0d6a12352f7ca8f38aadfd7e1df44453d933bf70c", size = 517354, upload-time = "2026-04-09T01:28:35.086Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/0f/c688f09d158d5d4bf3c83289b9d16d454d5ac001ae962a7501d4209dbcad/pyarmor_cli_core-8.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef4cd7ecf1aeca5748d221e548a6203f58ede46e3b1f31cc1fbba5170f1dc5c5", size = 409747, upload-time = "2026-04-09T01:28:40.159Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/f1/c97c393fa0f6431ac6537acd21f117025488b1254c255ae42da1809d2ae3/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux1_i686.whl", hash = "sha256:e95cfbaafee2eec3a3ef918c80f6ff6d6e26535c0b9f5ea615a97cf419a45603", size = 489761, upload-time = "2026-04-09T01:28:45.023Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/78/5874a8cecc9123475bcf5884037a6abd5859e4b93e89bc5eacc06dfd493f/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:2f42447c95f0de7df586a5899b395dcbf3193b5288a290f77dde90d4459700a6", size = 458105, upload-time = "2026-04-09T01:28:49.719Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/a0/73eb8557f0dd6f9910dfb1ff70ad31550d5b037dff78f80a006732efb085/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:635144791ddcf3a7bbd31ac10181828d40636e4b6a477ffefa0c6f4073524604", size = 604874, upload-time = "2026-04-09T01:28:54.871Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/6c/3eeab3e37748ae5354eb629ed5e03ee09c85c6abac8d77926d4d27507f52/pyarmor_cli_core-8.1.1-cp313-cp313-manylinux2014_armv7l.whl", hash = "sha256:248bfb04d86f38851bdc13652d5e447e0415128c0cdfbb04498802e624a88475", size = 1001331, upload-time = "2026-04-09T01:29:00.195Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/f3/9736f5049e0fff5a333a8e14058e5b6b89ef68f469bcc23f9001fd47ab42/pyarmor_cli_core-8.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a71b3b4ab8d07259b82c9fdad18e099d86b0880bce352949b834db825cbd697", size = 651229, upload-time = "2026-04-09T01:29:05.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/e5/988ebde164710de231a93127a1df25a278307bb8cd31e75b207266a3ae8b/pyarmor_cli_core-8.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:bc51089e37602a4e4c75b990448c44e25d7b1c7048805ad789863c889e80793f", size = 447196, upload-time = "2026-04-09T01:29:09.99Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/5c/4be2fe6a77477c6a82b776c147487030978fb3c6c825dfcecafe4cdaf1d1/pyarmor_cli_core-8.1.1-cp313-cp313-win32.whl", hash = "sha256:689356f95fec04a53f91a624b0d1f4bfc8547e2ddede6799fb1c728377a51e19", size = 460848, upload-time = "2026-04-09T01:29:15.702Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/d1/80a1085e94a8bcc3f1d8c6d1ef703c1fc35afe9a8f2b5f0676d74fc1a80d/pyarmor_cli_core-8.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:2e7b259c4dda700360030cf212b2cca625aaf9e7498dc9bfcbee9ef85acfec9b", size = 421343, upload-time = "2026-04-09T01:29:20.354Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/57/12936c3ea69e88481e71d4f61a878aa49b26181c0fed9282b590f46b9132/pyarmor_cli_core-8.1.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:4bfbfe564881bd467b2103b47b4fdfe883746e39651545280f4c815d409e45f7", size = 522708, upload-time = "2026-04-09T01:29:25.471Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/4c/ba326b377d0d698fa80864a478822e56acd0b1dd74db7b2645ca66e3c732/pyarmor_cli_core-8.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0ff3494acdacbec1565a1d98065ab2a2b47683778ad02b89b643008a1ffe90bd", size = 414399, upload-time = "2026-04-09T01:29:30.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/15/f6121674cca8487665bc4803da745a1f4e7f98d233bf33e659ca3dbfec29/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux1_i686.whl", hash = "sha256:2d905427cd23226f86998a28f6c79c849136dfb85e569e32b208ad7d75b24853", size = 495367, upload-time = "2026-04-09T01:29:35.223Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/6a/67121dc6f489120322bf4742774a3ebd05eb4b6d1ebcf971b34ea91ec2b4/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux1_x86_64.whl", hash = "sha256:4de138aacd465e0a0d4601b290d0555d47657f59b86d5eeec1bc660fd01e149a", size = 462060, upload-time = "2026-04-09T01:29:39.799Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bf/87/09d5c023474495dd3dbb789ecf538af1bcbe53c49cd07a703cfa5da03bde/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux2014_aarch64.whl", hash = "sha256:1777c7c2c7130a34b7c7125ddd828569e3f938fc91efc0db99fb0ee1eba88aa4", size = 609493, upload-time = "2026-04-09T01:29:44.322Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/55/c6f403c7a33900d1a194839e55677814a35ab33d10c4234394f7ad12c311/pyarmor_cli_core-8.1.1-cp314-cp314-manylinux2014_armv7l.whl", hash = "sha256:5172681e95bfbf4eb250d53e1ede75b270e7ec7c29a4accfb0acf766bc7b6351", size = 1004962, upload-time = "2026-04-09T01:29:49.616Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/bc/ce7146830a08306884929b53f785bb708df7c360a73a2a3687ff73533918/pyarmor_cli_core-8.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:06de0a47cb8ae86fe9270f3930d8e245fff73f87bd3c49cdc2af47c8ce1a5b06", size = 655381, upload-time = "2026-04-09T01:29:54.994Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/05/0fac5b9e9d37e1ea46ceae2733ab07d348b199257d99f69d290eae363ac0/pyarmor_cli_core-8.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7ae1c265c6560e869d355373fd86c2880a951db6bbf2bbdffec698d47c269be9", size = 450987, upload-time = "2026-04-09T01:30:00.065Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/84/173c89753b13037ad45fd6c220aeb12e055de261c8453025df9dc0c82b2c/pyarmor_cli_core-8.1.1-cp314-cp314-win32.whl", hash = "sha256:18a7f8aa4054395be9fe5b051727f2c32bdce74d609ef75cb869a9fd0396da90", size = 465415, upload-time = "2026-04-09T01:30:04.793Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/ce/32fb60e0971791c7e102ab87140d8c5da444e8b22b05614d7b63b7a90113/pyarmor_cli_core-8.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:9e50864097734b039060074b4913ffca73069162ff3c32556f3b594888129661", size = 424573, upload-time = "2026-04-09T01:30:09.704Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/02/7632897343d0b1ac3faa6e882f62880c4910d1331a46f1796816b99e9f8d/pyarmor_cli_core-8.1.1-cp315-cp315-macosx_10_14_x86_64.whl", hash = "sha256:eb3a63691a67aca10e2af7937baf0c88effc459c78c3c4ef1c48cf562376b830", size = 522819, upload-time = "2026-04-09T01:30:14.265Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/aa/09cce8ed725419d8a3c1a4a4b588b5f369094ef0b9768bc7e2d7fe855897/pyarmor_cli_core-8.1.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:75328d808fb790546272ba92440c8ae9e80f3c84cce3f423fefb00a9af230af0", size = 414398, upload-time = "2026-04-09T01:30:18.603Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/a9/844a2fcb09dc30994e03b5469ed80d6066e794828cd48af437da33029f8d/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux1_i686.whl", hash = "sha256:e0e0965e8a1d779485f747b93d6f4c7a20395553851921b0fd22ea89fb09287e", size = 495322, upload-time = "2026-04-09T01:30:23.59Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/1b/b169c06628dc9ac375c5d3f270cd742e344ddfa6dab5e00216a984ccfa01/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux1_x86_64.whl", hash = "sha256:3562142724c8cd48a6b161b35a7b4ebfc30820f08ca0adc28f8729077531c3f4", size = 461861, upload-time = "2026-04-09T01:30:28.655Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/98/8ab28d635e03d269234243c4ac2fd82d722435640d9be89e78cb6f4ba15f/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux2014_aarch64.whl", hash = "sha256:27c6e269131da709bcf1aac04ed8285eaa8a328f7682e98c4e93a81f97e2e4db", size = 609340, upload-time = "2026-04-09T01:30:33.652Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/68/f6e381d81e988077c6fdddcfa659ed30953640e863928575886815e1eefa/pyarmor_cli_core-8.1.1-cp315-cp315-manylinux2014_armv7l.whl", hash = "sha256:0cc8d7c337663eb9d108649ba296973284ff8b6fc0f83ae0ce347697533a983d", size = 1004837, upload-time = "2026-04-09T01:30:38.944Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/83/4958e7eb4a4dd27b024a034443636e7db418d06ee743eb09eecab2360e18/pyarmor_cli_core-8.1.1-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:00cccda40da819c45550625c910f836b9c768699ea40b04c058c3221270b514b", size = 655276, upload-time = "2026-04-09T01:30:44.053Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/d8/c8992fc6f66fcd8b29f8497ca352165bd9e0972a1f5371eb8c01aaca7a3e/pyarmor_cli_core-8.1.1-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:488be5b88a0c434fb17457972f6b7f681b38650ce078c3f19dced5b15091146d", size = 450926, upload-time = "2026-04-09T01:30:49.022Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/61/6cb081493c75662dd979a6cf8f6092d81605a9a59c2439a2ab77f3abfc1b/pyarmor_cli_core-8.1.1-cp315-cp315-win32.whl", hash = "sha256:a0f087fe8c388d178859dbc2a14e11e12bf8efc694f035e41d1dfc5b4a630052", size = 465352, upload-time = "2026-04-09T01:30:53.861Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/87/9daf968e6e6add10d089eef86b46a26c9908bfae74820bc97aacbf37d081/pyarmor_cli_core-8.1.1-cp315-cp315-win_amd64.whl", hash = "sha256:c1b96471bb1d9fc575ba526841829c6bf03cd9db5caabf5688518a29ed389870", size = 424475, upload-time = "2026-04-09T01:30:58.457Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/90/89c95f00699600a2c476b1c80e97df5ea1f09b995a8abfc0a8f118f0bf38/pyarmor_cli_core-8.1.1-py3-none-any.whl", hash = "sha256:06eb9771c7d1f0ad7c00884f13ea42edfbffab8b28ef718ce197c0b4c69d97d5", size = 5177, upload-time = "2026-04-09T01:33:22.702Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.21.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/4d/ec706c3fcf39e26888c35b39615ff4d5865d184069666c47492cff1fbe50/pyinstaller-6.21.0.tar.gz", hash = "sha256:bb9fab705983e393a2d1cac77d6972513057ad800215fd861dc15ff5272e98fd", size = 4061519, upload-time = "2026-06-13T14:15:06.25Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/4a/53cf98bf66daed012dc9cd78c8203f19a675d696f2fc12afcf8c5049a0e0/pyinstaller-6.21.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:327d132389f37912609e01be62810cf96b5aa95b613903e4b8692e0d12fb0eda", size = 1052350, upload-time = "2026-06-13T14:13:55.88Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/83/b591295c352ef464c50b4c6ffff1c4f771d875c9e833f578d1b9f564f6b3/pyinstaller-6.21.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7071d4b094d5b40deeef5fa3d3b98a1b846087f7562b49209663d5f9281fe251", size = 748477, upload-time = "2026-06-13T14:14:00.327Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/8f/88fff4e403873b1e22286911350e75ff00db014aa08e57045da9d4328993/pyinstaller-6.21.0-py3-none-manylinux2014_i686.whl", hash = "sha256:6b6374d652107dd4a2eeece903ff82bb4045bb5e1006c5a158a6dcdbefe84bf2", size = 760877, upload-time = "2026-06-13T14:14:04.836Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/13/f0e48fbdfd1d05d948157121cea8b1b823dcb89efe6934b71fdd8bdb3f0f/pyinstaller-6.21.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4e3108b3f02384560da70e39b8bf22b0ad597d02bd68a40d76ea91c1cfa00cad", size = 759194, upload-time = "2026-06-13T14:14:10.61Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/d5/ea7878cf9924ed30d946d8288777424e6d069d94f5bde56b4d0890069664/pyinstaller-6.21.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:697532279f535ad572bda613db4f821540e235c7854ca6da4d3bf0373f4415ee", size = 754979, upload-time = "2026-06-13T14:14:15.226Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/09/51b8905714b733bac66dbc041a7821372d70d888d273ae474c4037d4202d/pyinstaller-6.21.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:605169523a6b5ace39f13dfbff21add9f2bc43df99c7daf9394fefb2c45e8b6f", size = 754812, upload-time = "2026-06-13T14:14:20.264Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/43/d77779439d8c6c2e27a77bcfbd1d5cc0f568ebb611bb472b11af81b5f177/pyinstaller-6.21.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:5fa56746c1e76f93634d018502301378a2d0c382553d37d8c3c34ff436c12dd1", size = 753887, upload-time = "2026-06-13T14:14:25.268Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/8f/c22df1f6837784ac349057ba693f08e7b1ca7a0e06f9c33c63bc6280007b/pyinstaller-6.21.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:42395ec76df8e8120c36b13339d9db8cab83e316a12839ee303cc00fc941bb74", size = 753779, upload-time = "2026-06-13T14:14:29.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/76/1ce8a27ce62ba8cf3a87c9ce6d575610f4e55d7cb0123e7512fc3f4b921a/pyinstaller-6.21.0-py3-none-win32.whl", hash = "sha256:c6b28d30d8fd99ce162ff3aab5013ed44dbfb747566b1f01b9bed7964d7c14e9", size = 1336462, upload-time = "2026-06-13T14:14:35.785Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/fa/ca1d7e5257dd8566a9dfc0dfb02f8a8075eeb53d4b2d3c579f1276759042/pyinstaller-6.21.0-py3-none-win_amd64.whl", hash = "sha256:7fae06c494ce0ebfe6bd3055c0e409def884f63af2e3705d06bd431ad9237fc7", size = 1397487, upload-time = "2026-06-13T14:14:42.328Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/75/21b51523ce8d96629b71311775a0a65f5f5a872124ab0de33e5c848f8bff/pyinstaller-6.21.0-py3-none-win_arm64.whl", hash = "sha256:f13c95c9c03fb567217135919f93815c305813126780b0ed6e0123cb8acaf025", size = 1346094, upload-time = "2026-06-13T14:14:48.914Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2026.6" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/5b/c9fe0db5e83ee1c39b2258fa21d23b15e1a60786b6c5990ee5074ead8bb6/pyinstaller_hooks_contrib-2026.6.tar.gz", hash = "sha256:bef5002c32f4f50bd55b005da12cff64eca8783e7eaf86a06a62410164bab725", size = 173354, upload-time = "2026-06-08T22:37:16.152Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/31/f2d7343d8ed5f7c4678377886f6ce533e6eaaa131b252ce950114c2a7efa/pyinstaller_hooks_contrib-2026.6-py3-none-any.whl", hash = "sha256:fd13b8ac126b35361175edacd41a0d97080b75dd5f4b594ecefefff969509dd3", size = 457159, upload-time = "2026-06-08T22:37:14.722Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { 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 = "ruff" +version = "0.15.20" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.19.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/da/4bef7ce7bb989b222aa4785a413896dbec53306dfc59c6ce7d16a7ffbd6a/s3transfer-0.19.1.tar.gz", hash = "sha256:d3d6371dc3f1e5c5427b2b457bcf13bcf87bec334c95aed18642eae61f6926f3", size = 165354, upload-time = "2026-07-10T19:32:04.849Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/23/e84c64ad0e8bc59cd1b2ef98def848deff0ef3456c542afe74d51e9e8c85/s3transfer-0.19.1-py3-none-any.whl", hash = "sha256:d5fd7005ee39307455ad5f310b5ea67f4b1960d7fed5b3671ee50c249de675de", size = 90072, upload-time = "2026-07-10T19:32:03.673Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sqlite-vfs" +version = "0.1.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/ff/9cbb7f704d59f88ae048bb84a3de19b5d6dd0c1ae21af53396723af18d14/sqlite_vfs-0.1.0.tar.gz", hash = "sha256:cd6cf5cb7d60c1983d9befdc6c4cce8ab050fc4fd215e5e63ece1d2d28f4f9bb", size = 57435, upload-time = "2025-11-08T08:16:01.715Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/26/a3f51ea9d10efe2c4f85cac78dcbd898b437d2331cf25d470f804e4498b7/sqlite_vfs-0.1.0-py3-none-any.whl", hash = "sha256:c6e27a35e504bb6360fb0d9083d79572367a903788b3b14aea5f1188130e9314", size = 10768, upload-time = "2025-11-08T08:15:18.588Z" }, +] + +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.4.4" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/81/5b/879b2f932adfa7a053c360d50bc896c977fa6426109185f7c12ebdd0cb9d/tzlocal-5.4.4.tar.gz", hash = "sha256:8dbb8660838688a7b6ba4fed31d18dedf842afb4d47ca050d6d891c2c15f3be4", size = 31170, upload-time = "2026-06-29T08:03:40.026Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/a4/017a7a6cbe387d961a688ec31364ae60a5c4e22c96ae9921b79a947c855d/tzlocal-5.4.4-py3-none-any.whl", hash = "sha256:aae09f0126a8a86fa736be266eb4a471380d26a0de3bc14844e7821fee3e2a15", size = 18115, upload-time = "2026-06-29T08:03:38.666Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.51.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, +] + +[[package]] +name = "websockets" +version = "16.1" +source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" } +sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/02/b9a097e1e16fee4e2fd1ec8c39f6a9c5d6257bae8fa12640caf869f54436/websockets-16.1.tar.gz", hash = "sha256:299468cbe42e2b9981134c7c51d99387d8a7bf562b00183b3eec53f882846dad", size = 182530, upload-time = "2026-07-10T06:32:57.734Z" } +wheels = [ + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/52/748c014f07f4e0e170c8932de7e647a1511d5ab3049cd978797136aee577/websockets-16.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b6aa3f7ad345cf3862c21f4fbf2ef5e14d911348476c2845e137c091fe3a3f0b", size = 179798, upload-time = "2026-07-10T06:31:09.664Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/5e/2a2e64d977d084e49d37c187c26c056daaff41965be7300cd5dbde6f8b07/websockets-16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b43fcfb521ac2f34ba80b7b8ea16303e4ad82dd8af667bf40839ad3a5d37b164", size = 177478, upload-time = "2026-07-10T06:31:11.072Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/12/5b85b4e75d697e548a94962ce5c036b05dd21cb9545759d555c5586422fc/websockets-16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2bd3e12cd9afbe2baedae0b1eeade8ba64329b60fe2f9abdc966bd10fd2c2ef5", size = 177746, upload-time = "2026-07-10T06:31:12.386Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/62/79b1c8f0cee0da648b4899e1c5b0dbd3aa59846985136a54854db6827ab4/websockets-16.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f41979c8623df9bd30d949d82010a8fda5c56ff12cd8508a5b7272b6d4b53a", size = 187345, upload-time = "2026-07-10T06:31:13.754Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/34/b7c5c52c2f24280e1c017acb7ad491a566750a5cceca7f3cf999373bba21/websockets-16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a24d1f35aef07d794a16c853c688e74956c50239bec37b4f2de080056046419b", size = 188581, upload-time = "2026-07-10T06:31:15.075Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bc/37/604193bebcbeffe96fdf795960b83a15d600880c64dc17ec9c31c5b3427d/websockets-16.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c64c024ddf7a35331b21fcddb562a039c275d2c82e8c2d12939e7da23997270", size = 191362, upload-time = "2026-07-10T06:31:16.395Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/b4/5ee27575b367d7110d4d13945e2a9de067ec84dc71e54b87f01e38550d9a/websockets-16.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3e99757f5baafe20fc598e202ea6f5b0b265186ad38d0a17bd8beca16296955", size = 189216, upload-time = "2026-07-10T06:31:17.776Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/22/3e2dcc78d85fc5d9d814895ce6d07d0dfacc0f6aaa1d151f2b8c8d772299/websockets-16.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:353f3bc6e058ac1ccab4b3588e8598837a8c04cfc8351233e6d523be675d844c", size = 187971, upload-time = "2026-07-10T06:31:19.152Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/2f/cd271717b93d5ee19626cb5e38a85baab745c86e33db7c31a3ac729b31b8/websockets-16.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0352f5b38b40e857b6428d468fa21dbb4dd4a567d933c26d9831b4efe1b92f43", size = 185381, upload-time = "2026-07-10T06:31:20.665Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/91/6ad6f2f1426317b5001bd490534208c7360636b35bac1dec2e0c22bfc40e/websockets-16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70bd789afab579602968c39f21cb925466505f3edff22f0ae852bca54978a4f9", size = 188015, upload-time = "2026-07-10T06:31:22.024Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/6d/533733132ab4c07540efd4a8f0b9a435d3a5059b2f26cc476ace1abf7f45/websockets-16.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d0fb4b46f121eccd539353baebd1083a8767a9a351109453d1d1caecd1ba40c2", size = 186619, upload-time = "2026-07-10T06:31:23.376Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/73/16c059f3d73b3331eba10793704afa4faa9939234fb08ef7dca35794e8f0/websockets-16.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c14b6634af01541e4efe2954fd8f263386f7aa6d37c01e55dd8109fd17661452", size = 188497, upload-time = "2026-07-10T06:31:25.024Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/89/9a8fae7dd2acdcfb1a8844c29fe42b518a04b64fce38a0923b6290e452f1/websockets-16.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a58532c49a851bcb481e58c1be23b315c17fe2fbbed509d75aeea12f543d2c15", size = 186051, upload-time = "2026-07-10T06:31:26.291Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/40/b240c7dd6a0e0c59c1f68377cc3015263521080c327c15f5e753c1f6d378/websockets-16.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4e969170c3b08e1d8dabd990fef1fa702c4233aeaabec33f871806e444f6a0e4", size = 187029, upload-time = "2026-07-10T06:31:27.605Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/35/524e3fac40e47d6fdcf6c4b2c95ef1bc8a97e01593c90eff86621df7b716/websockets-16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff9b000064b88787ba9f7a3cb2af2b68a658ca5aad76458a46469e7124b678a0", size = 187308, upload-time = "2026-07-10T06:31:28.927Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/00/13/56840cf62c8859af6ba22b9529da937332468c80f32b598753e8a66d3990/websockets-16.1-cp312-cp312-win32.whl", hash = "sha256:b9f5d83f80f4d7c4bba6d97f3755ac05850c784dce0fd2ab371c4e41172f53ff", size = 180161, upload-time = "2026-07-10T06:31:30.316Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/ff/87eb9eb44cb62424a8d729834f2b0515a47e2669fabec29820268f4d50a1/websockets-16.1-cp312-cp312-win_amd64.whl", hash = "sha256:6852c9f653966c16109d3b6f31181fd734f7914927e3f0fa1117af7a18c9aa21", size = 180462, upload-time = "2026-07-10T06:31:31.708Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/63/df158b155420b566f025e75613424ad9649a24bcb0e9f259321ab3d58bea/websockets-16.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b0232ed141cec3df2af5a3959a071c51f40036336b0d37e17faf9ef52fc73e47", size = 179791, upload-time = "2026-07-10T06:31:33.108Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/cf/00fe9414dfeafa6fe54eae9f5716c8c8e9ac59d192be3b893c096d395846/websockets-16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a71b73d143991714144e159f767b698f03c4a70b8a65ae1733b650cff488045b", size = 177472, upload-time = "2026-07-10T06:31:34.522Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/76/b10633424d40681b4e892ffd08ca5226322b2426e62d4ab71eae484c3a32/websockets-16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:187323204c3b2fc465e8fc2609e60437c521790cb9c1acb49c4c452a33e57f37", size = 177737, upload-time = "2026-07-10T06:31:35.964Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/61/d3bb03b2229bb1afd72008742d586cf1ea240dce64dd48c71c8c7fd3294c/websockets-16.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9dba74233c8c3ce368850818c98354dad2570f57231b3fd3bd00d7aa57628881", size = 187403, upload-time = "2026-07-10T06:31:37.496Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/26/16/cc2e80478f688fc3c39c67dc1fac6a0783858058914ebc2489917462cb42/websockets-16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63339bc8c63c86a463177775cb7c677691f5bcfac7b3b2f01b286d42acd41600", size = 188639, upload-time = "2026-07-10T06:31:38.86Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/d6/ad87b2507e57de1cbf897a56c963f2925962ed5e85fbe06aaa83ced27acd/websockets-16.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23e545ea8ae4263e37cdfd4e22a217f519e48e432728bc461185bbf585f38a83", size = 190078, upload-time = "2026-07-10T06:31:40.218Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9e/1a/5b37b3fd335d5811f29fc829f2646a3e6d1463a4bf09c3100708684c766e/websockets-16.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2237081454846fb40403a80ba86d82e2038b9c45865ab96af0abe7d002a91045", size = 189267, upload-time = "2026-07-10T06:31:41.523Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/98/06afc33e9450d4230f94c664db78875d90f5f6a5fb77f0bc6ec15ae74e1c/websockets-16.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5f5218de1ed047385ca53744caba9435d65f75d008364970a3fae95a05812cf9", size = 188022, upload-time = "2026-07-10T06:31:42.838Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/bf/42fef5d5887c18cf2d148b02debf56cecb9cfbffc68027cde9b12c8f432c/websockets-16.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75c98e3920039d0edff03b74478ada504b7ce3a1bc406db2cabfca84320f7baf", size = 185435, upload-time = "2026-07-10T06:31:44.219Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/9b/8021c133add5fe40ed40312553a6cd1408c069d7efe3444ad483d4973ed3/websockets-16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1facd189d8190af30487a55b4c3688484dd50801628a3b5b2ccd26db08e67057", size = 188080, upload-time = "2026-07-10T06:31:45.986Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/69/54/1e37384f395eaa127383aab15c1c45e200890a7d7b99db5c312233d193e0/websockets-16.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cc0c6a6eef613c7da32d4fb068f82ef834b58134f6a16b54e6c1e5bf9529ab3d", size = 186678, upload-time = "2026-07-10T06:31:47.449Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/79/1caeacab5bc2081e4519288d248bc8bd2de30652e6eaa94be6be09a1fe5b/websockets-16.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ad9411eded8988b879be6038206698bf7106c85a78f642c004485bcb95be17eb", size = 188554, upload-time = "2026-07-10T06:31:48.886Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/83/b3dca5fad71487b726e31cb0acf56f226792c1cc34e6ab18cbf146bd2d74/websockets-16.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cd68f0914f3b64694895bc5e9b14e8b447e41d7bf5ffaf989bb8dcb5e2dfdce7", size = 186109, upload-time = "2026-07-10T06:31:50.508Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/0b/8f246c3712f07f207b52ea5fb47f3b2b66fafec7303162644c74aed51c6a/websockets-16.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fef2debfe7f7ebdda12176f26166f95b7af17af05ba06150fcf889032e0213e9", size = 187061, upload-time = "2026-07-10T06:31:51.861Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/eb/27d6c92a01696b6495386af4fc941d7d0a13f2eab2bf9c336111d7321491/websockets-16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3cd6c9b798218798f4bb7b2e71c38f0e744bb94ca537b13376f88019d46384d", size = 187347, upload-time = "2026-07-10T06:31:53.246Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/d5/eeee439921f55d5eaeabcea18d0f7ce32cdc39cb8fc1e185431a094c5c7b/websockets-16.1-cp313-cp313-win32.whl", hash = "sha256:84c170c6869633536921e4474b1cce7254c0c9b0053ef5725f966cee47e718e4", size = 180149, upload-time = "2026-07-10T06:31:55.058Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/03/971e98d4a4864cf263f9e94c5b2b7c9a9b7682d77bfbba4e732c55ee85a9/websockets-16.1-cp313-cp313-win_amd64.whl", hash = "sha256:bef52d327d70fa75dad93ee61ea2cb1d1489aca9f35c188833563f5a3b4df0a5", size = 180458, upload-time = "2026-07-10T06:31:56.767Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/e6/da1dc11507f8118145a81c751fe0c77e5e1c11b8554496addb39389e2dc2/websockets-16.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f881fca0a45dd6789939bd6637cd98169b92f1c3fdc78262f2cb9ec2cb1f324e", size = 179833, upload-time = "2026-07-10T06:31:58.19Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6e/ac/c0d46f62e31e232487b2c123bc3cfd9a4e45684ca7dc0c37f0987f29baae/websockets-16.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c379d5b207d3a7f0ba4c2e4602a895b0bcc63fb5f5371a4ae7fbddb03b672b", size = 177524, upload-time = "2026-07-10T06:31:59.563Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4a/33/abd966074b34a51e4f134e0aaed80f5a4a0a35163ea5ac58a1bc5a076d23/websockets-16.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:98ab58a4faa72b46da0127ccc1931dcbfc0985b0778892300a092185910c4cbe", size = 177743, upload-time = "2026-07-10T06:32:00.959Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/30/646e47b8a8dff04e227bdab512e6dde60663a647eeac7bbd6edddd92bbc5/websockets-16.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e9c4e369fc181b2d41a99e01477215cecdc8546a39f7d41a59cc0a7065a0b09", size = 187474, upload-time = "2026-07-10T06:32:02.54Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/72/890ab9d77494af93ea65268230bfbc0a90ba789401ed7a44356a44785644/websockets-16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0704df094b2d5fa7f6f410925a594c2a5c9a09167731a76292e5410934208209", size = 188717, upload-time = "2026-07-10T06:32:04.156Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/aa/baedbbaa6bf9ed6029617ed5e8976535bd805f483ca9b3484e7ad9ee08bf/websockets-16.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b22b1f4950f6ab7126623329c3b47b3b90a14c05db517f2db2a026ad6c928352", size = 190090, upload-time = "2026-07-10T06:32:05.822Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/4f/d813ec94e18002571ef4959d87a630eff6e01b72a51bcb0832b75ae8c51a/websockets-16.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1ae4a686a662964a6671069f84f7f908cc3475e782227726b0c622c715962105", size = 189320, upload-time = "2026-07-10T06:32:07.223Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/3c/8ec52a6662f3df64090fba28cd521d405d54759268d8e820477037e8c80d/websockets-16.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:856bdd638f8277f86465057bfdd4da097c73058fb0f9d2bd5baea29e2bf2d367", size = 188068, upload-time = "2026-07-10T06:32:08.586Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/7f/f0ae6042b14f86fa5f996c6563ea4cf107adc036ccbedc9d4f418d0095f9/websockets-16.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9003a1fde1c21a322a3ca3fa0c4bda8c639da81dbc925162766086643b05ba87", size = 185493, upload-time = "2026-07-10T06:32:09.968Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/ad/5ffc53af9939c49fd653d147fa5b8f78ced1f6bce6c49a7446860945b0ce/websockets-16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39e947b1f5fdab045174306e3916785bf3ed537648acc1549827c08c33b10953", size = 188141, upload-time = "2026-07-10T06:32:11.434Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/62/729206c0ee577a4db8eae6dd06e0eef725a1287c6df11b2ef831d003df31/websockets-16.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5dd0e666b5931c0509cf65714686a1c5126771e663a79ac5d40da4f58b1f9502", size = 186653, upload-time = "2026-07-10T06:32:12.845Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/86/e8806a99ec4589914f255e6b658853fe537bf359c05e6ba5762ad9c27917/websockets-16.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a0285df7925657ad65a65fb8dc330808bce082827538fd50ef45fa12d1fc5bca", size = 188614, upload-time = "2026-07-10T06:32:14.236Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/38/ac554e2fc6ff0b8deeff9798b92e7abd8f99e2bd9731532e7033de208220/websockets-16.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:82d1c2cab3c133e9d059b3a5420bed9376bd30e21c185c63dda4ddadf6ddda47", size = 186165, upload-time = "2026-07-10T06:32:15.626Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/c5/4ef4d8e53342f94f3c49e1ae089b32c1e8b3878e15e0022c7708c647f351/websockets-16.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c39907f1eaf11f6277def65aa02d68f30576b693d0c1ca332aafa3caa723ac6d", size = 187119, upload-time = "2026-07-10T06:32:17.114Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/33/4788b1dd417bd97eeb2698af3b9df6775ac656f96e9987da0419a067602f/websockets-16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:45c5ea55446171949eb99fd34b771ceddd511ca21958d40d0197ced33159e5ee", size = 187411, upload-time = "2026-07-10T06:32:18.629Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/30/38/00d37aad6dc3244ce349e2864815362e50b3cfc00cac28d216db20efe40f/websockets-16.1-cp314-cp314-win32.whl", hash = "sha256:b8ef8b1c8d6bd029a475ac432e730fba2dfd456715d26c473e2a82291024b99c", size = 179822, upload-time = "2026-07-10T06:32:20.233Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/37/2a8cb0eaddee5eaebda47a90a3ba0898d1ce3d866b02a4857fea17d82e5b/websockets-16.1-cp314-cp314-win_amd64.whl", hash = "sha256:7358ff21632b5d062707f73e859c824f1c3807e73d8ca25e71caca7c4cdcf145", size = 180167, upload-time = "2026-07-10T06:32:21.749Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/5a/262ad5fcaef4198997b165060f09a63f861e76939b1786ab546ccc3f8120/websockets-16.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d0f38f4c3e9b359e257c339c2cc1967ccaeedb102e57c1c986bdce4bf4f32268", size = 180166, upload-time = "2026-07-10T06:32:23.278Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/c7/36377db690f4292826e4501a6dec2801dc55fd1cf0405923b04937e478df/websockets-16.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3c3d2cbd1602593bad49bd86fa3fbb25407d87a3b4bf8857c0ac5ac4914e1901", size = 177697, upload-time = "2026-07-10T06:32:25.164Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/c7/07171abce1e39799a76f473608580fe98bd43a1230f5146159622c02bccf/websockets-16.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:36069b74671e7e667f48a7484249f84c45a825a134c8b1bdc01875d0daa10d79", size = 177902, upload-time = "2026-07-10T06:32:26.564Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/17/c831f48e250bc4749f57c00dcce73337c41cd32f6d59a64567b84e782601/websockets-16.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:587f83c2ce8a5d628e166384d77fa7f0ac69b9007d515ab442123e6615aa8da3", size = 187766, upload-time = "2026-07-10T06:32:27.981Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2c/2e/4dfe63e245b0ecfaf470cf082d25c6ce35808159135fd88c82653a6b11ab/websockets-16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6db7972d52bc1b66cefe2246902e256cbaebc9ba8a45eac09343d7eb6671b2", size = 188939, upload-time = "2026-07-10T06:32:29.365Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/e5/5faf65aebd9562f6b4bc473d24ce38cc56f84eb5f5bee66ed9b86733f93c/websockets-16.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e7d6014888a0632e1ed7a4095248bb3095232999447f2d83bfb1900987dd9ed9", size = 191081, upload-time = "2026-07-10T06:32:30.868Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/cd/2634f2f2c0556c1aae6501ed6840019cc569dd6fdbcac6494378daea4dc0/websockets-16.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cb074d150e4ad2a77aa8a332c2be85f3f64f2681519d2570c1225c12c9821ff", size = 189513, upload-time = "2026-07-10T06:32:32.399Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/bb/2c700b51196104f09715b326b1f092ed25326bdf79a03e00a4842e503743/websockets-16.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d19c9067e1fe9490f974bffbc0e443b80a7674c5efb4980c429cc00771f07c5a", size = 188240, upload-time = "2026-07-10T06:32:33.897Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/20/86283636e499a1a357fa9441f690ba34f255e731f2fea174132b3b762b57/websockets-16.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d440ff0c6c7469ad59c0a412c383c235935b43635e89425e3f6a0c36de90c31b", size = 185955, upload-time = "2026-07-10T06:32:35.279Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/23/d7fb734b0095d43bc7f1c9f68afd50adb4176e7e513403e8c70ad7daa4fa/websockets-16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8613129a2533f08de24505e69a3e403cedaadae49abdb043c4d170ca71b7e4bd", size = 188491, upload-time = "2026-07-10T06:32:36.673Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6a/5e/168a192689db468405ecf3b8e4a2c18811936b0724d017ad7e6d252734f0/websockets-16.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a5bf9c23f197b4ec88290fd5463f33db67362a1bb10f85fc2e8e7627f0ddab97", size = 186983, upload-time = "2026-07-10T06:32:38.207Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7e/9b/66795fa91ebe49019ebe4fa910282172252e37046b80e08fc52e0c365150/websockets-16.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:520b0fd0395f075febb283c76755af724ab9fd19dffa4f3bfd18cb4e622790a3", size = 188890, upload-time = "2026-07-10T06:32:39.545Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/32/126bbc844be5afb3613fd43211dac10a9645f4cf39741d04acaa2ec7030c/websockets-16.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7143aa09a67e1c013be44e81a88dfe90fc6244198ab86c7edd064152cf619805", size = 186583, upload-time = "2026-07-10T06:32:41.038Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/b9/0b5db9cbcf6e4970db4496893244a8d92e07f71a8ef27cf34b08aa02fef1/websockets-16.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:7acb811fad08e611755800d1560e395c67e11a6bd563598ea6abb319afb86938", size = 187353, upload-time = "2026-07-10T06:32:42.501Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/2e/254b2131a10d831b76e2c18dfe7add9729c6292c674a8085bf8de01ad151/websockets-16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c5cf88e3faa2f7931bc6baeee7599c97656a3f6ac7f831f4fccba233e141783a", size = 187784, upload-time = "2026-07-10T06:32:43.929Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/dc/e7288aa8e3ac5a88a0924619984d663c1abf2a87d0ea98290c66fdaee0ec/websockets-16.1-cp314-cp314t-win32.whl", hash = "sha256:589f8842521c8307684ce0b40ce4ad70c5e0aa46484c6f1225a94ef4b8970341", size = 179947, upload-time = "2026-07-10T06:32:45.495Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d3/de/37edf1260ff0fbbd2f82433489c4cfbe799ac2ff21355331609879329fe6/websockets-16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c0e0857c30bbbc2bb5c30687508f0b7ec19aa026cd9f2ff8424d0fee42dcc07", size = 180291, upload-time = "2026-07-10T06:32:47.119Z" }, + { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/58/bd83247f39ddc26ffc2c24eb05087a3b749e00cb4509fc6d19daa23c8495/websockets-16.1-py3-none-any.whl", hash = "sha256:c5149dfe490ec7e5ee5dbf624c642fb725f93a5575c7f00ab594ca9eddb8dd81", size = 174031, upload-time = "2026-07-10T06:32:56.079Z" }, +]