diff --git a/python_auth_runtime/README.md b/python_auth_runtime/README.md index c89458d..b42c761 100644 --- a/python_auth_runtime/README.md +++ b/python_auth_runtime/README.md @@ -1,56 +1,75 @@ # @clawd/auth-runtime-py -Python 版本的 OpenClaw Auth Runtime,基于 `~/clawd/skills/_shared/auth-runtime` 的 TypeScript 实现。 +Python 版本的 OpenClaw Auth Runtime。 + +**注意**: 本模块**不加载 .env 文件**,只从环境变量读取配置。 + +加载 `.env.local` 应该在具体的 skill 中实现。 + +## 项目结构 + +``` +your-skill/ +├── .env.local # 敏感配置(不提交到 git) +├── scripts/ +│ ├── main.py # 加载 .env.local 并调用 auth_runtime +│ └── load_env.py # 从 python_auth_runtime 复制 +└── pyproject.toml +``` ## 安装 -### 方式 1: 作为本地包安装(推荐) - ```bash -# 在具体 skill 的目录中 uv pip install /path/to/python_auth_runtime ``` -### 方式 2: 作为文件依赖 - -在 `pyproject.toml` 中添加: - -```toml -[project] -dependencies = [ - "python-auth-runtime-py @ file:///path/to/python_auth_runtime", -] -``` - -### 方式 3: 复制源码 - -直接复制 `src/python_auth_runtime` 到你的项目中: - -```bash -cp -r /path/to/python_auth_runtime/src/python_auth_runtime ./your-skill/ -``` - ## 使用方式 -### 1. 设置环境变量 +### 步骤 1: 在具体 skill 中加载 .env.local -```bash -export CLIENT_KEY="your-client-key" -export AUTH_BASE="https://api-gw-test.yuanwei-lnc.com" # 可选 -``` - -### 2. 导入并使用 +参考 `~/clawd/skills/1688-product-master/scripts/run.ts` 的 `loadEnvLocal()` 实现: ```python -from python_auth_runtime import create_env_config, get_access_token, request_api_with_auto_refresh +# your-skill/scripts/main.py +from pathlib import Path +import os -# 创建配置(自动从环境变量加载) +def load_env_local(): + """加载 .env.local 文件到环境变量""" + script_dir = Path(__file__).parent + env_local_path = script_dir.parent / ".env.local" + + if env_local_path.exists(): + with open(env_local_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + eq_index = line.find("=") + if eq_index > 0: + key = line[:eq_index].strip() + value = line[eq_index + 1:].strip() + # 去除引号 + if (value.startswith('"') and value.endswith('"')) or \ + (value.startswith("'") and value.endswith("'")): + value = value[1:-1] + # 只设置尚未存在的环境变量 + if key not in os.environ: + os.environ[key] = value + +# 在 main() 函数开始时调用 +load_env_local() +``` + +### 步骤 2: 使用 auth_runtime + +```python +from python_auth_runtime import create_env_config, request_api_with_auto_refresh + +# 创建配置(自动从环境变量读取) config = create_env_config() -# 获取访问令牌(带缓存) -token = get_access_token(dry_run=False, config=config) - -# 发送 API 请求(自动处理令牌刷新) +# 调用 API response = request_api_with_auto_refresh( method="POST", url=f"{config.auth_base}/ecom/tasks/scrape", @@ -65,62 +84,44 @@ if response.status == 200: print("商品价格:", data["price"]) ``` -## API 参考 - -### 配置 - -#### `create_env_config() -> EnvConfig` - -从环境变量创建配置: - -| 环境变量 | 默认值 | 说明 | -|---------|--------|------| -| `AUTH_BASE` | `https://api-gw-test.yuanwei-lnc.com` | 认证基础 URL | -| `CLIENT_KEY` | **必需** | 客户端密钥 | -| `AUTH_CACHE_DIR` | `/tmp/skill-auth-cache` | 缓存目录 | -| `AUTH_MIN_TTL_SEC` | `60` | 最小令牌 TTL(秒) | - -### 令牌管理 - -#### `get_access_token(dry_run, config) -> str` - -获取访问令牌(带缓存) - -#### `refresh_access_token(dry_run, config) -> str` - -刷新访问令牌(绕过缓存) - -### API 请求 - -#### `request_api(method, url, auth_token, body) -> ApiResponse` - -发送 HTTP 请求 - -#### `request_api_with_auto_refresh(method, url, dry_run, config, body) -> ApiResponse` - -发送 API 请求并自动刷新令牌 - -## 与 TypeScript 版本对比 - -| 特性 | TypeScript | Python | -|------|-----------|--------| -| 包名 | `@clawd/auth-runtime` | `@clawd/auth-runtime-py` | -| 安装方式 | `bun add file:../_shared/auth-runtime` | `uv pip install /path/to/python_auth_runtime` | -| 导入 | `import { ... } from '@clawd/auth-runtime'` | `from python_auth_runtime import ...` | -| HTTP 客户端 | `fetch()` | `requests` | - -## 示例项目 +### 完整示例 ```python -# your-skill/main.py -from python_auth_runtime import create_env_config, request_api_with_auto_refresh +#!/usr/bin/env python3 +# your-skill/scripts/main.py import os +from pathlib import Path +from python_auth_runtime import create_env_config, request_api_with_auto_refresh + +def load_env_local(): + """加载 .env.local 文件到环境变量""" + script_dir = Path(__file__).parent + env_local_path = script_dir.parent / ".env.local" + + if env_local_path.exists(): + with open(env_local_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + eq_index = line.find("=") + if eq_index > 0: + key = line[:eq_index].strip() + value = line[eq_index + 1:].strip() + if (value.startswith('"') and value.endswith('"')) or \ + (value.startswith("'") and value.endswith("'")): + value = value[1:-1] + if key not in os.environ: + os.environ[key] = value def main(): - # 从环境变量加载 CLIENT_KEY + # 加载 .env.local + load_env_local() + + # 创建配置 config = create_env_config() - # 调用 1688 API + # 调用 API response = request_api_with_auto_refresh( method="POST", url=f"{config.auth_base}/ecom/tasks/scrape", @@ -129,35 +130,42 @@ def main(): body={"url": "https://detail.1688.com/offer/123.html"}, ) - if response.status == 200: - print("成功:", response.body) - else: - print("失败:", response.status, response.body) + print(f"状态:{response.status}") + print(f"响应:{response.body}") if __name__ == "__main__": main() ``` -## 环境变量配置 +## 环境变量 -在项目根目录创建 `.env` 文件(不提交到 git): +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `CLIENT_KEY` | **必需** | 客户端密钥 | +| `AUTH_BASE` | `https://api-gw-test.yuanwei-lnc.com` | 认证基础 URL | +| `AUTH_CACHE_DIR` | `/tmp/skill-auth-cache` | 缓存目录 | +| `AUTH_MIN_TTL_SEC` | `60` | 最小令牌 TTL(秒) | + +## 命令行参数覆盖 + +可以在命令行设置环境变量,优先级高于 `.env.local`: ```bash -# .env -CLIENT_KEY=your-client-key-here -AUTH_BASE=https://api-gw-test.yuanwei-lnc.com +CLIENT_KEY="override-key" python scripts/main.py ``` -然后在代码中加载: +## 测试 -```python -from dotenv import load_dotenv -load_dotenv() # 加载 .env 文件到环境变量 - -from python_auth_runtime import create_env_config -config = create_env_config() +```bash +cd /Users/xiaolongxia/Documents/ai-build-app/skills/excel-toolkit/python_auth_runtime +uv run python scripts/example_usage.py ``` -## 许可证 +## 与 TypeScript 版本对比 -MIT +| 特性 | TypeScript | Python | +|------|-----------|--------| +| 模块 | `@clawd/auth-runtime` | `python_auth_runtime` | +| .env 加载 | skill 自己实现 (`loadEnvLocal()`) | skill 自己实现 (`load_env_local()`) | +| 环境变量 | `process.env` | `os.getenv()` | +| 缓存 | `/tmp/skill-auth-cache` | `/tmp/skill-auth-cache` | diff --git a/python_auth_runtime/scripts/example_usage.py b/python_auth_runtime/scripts/example_usage.py new file mode 100644 index 0000000..966a26c --- /dev/null +++ b/python_auth_runtime/scripts/example_usage.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +示例:如何在具体 skill 中使用 auth_runtime + +参考:~/clawd/skills/1688-product-master/scripts/run.ts +""" + +import sys +from pathlib import Path + +# 添加当前目录到路径 +sys.path.insert(0, str(Path(__file__).parent.parent / "src")) + +# 步骤 1: 加载 .env.local +from load_env import load_env +load_env() # 加载 .env.local 或 .env + +# 步骤 2: 导入 auth_runtime +from python_auth_runtime import create_env_config, request_api_with_auto_refresh + +def main(): + print("=" * 60) + print("Auth Runtime 使用示例") + print("=" * 60) + + # 步骤 3: 创建配置(从环境变量读取) + config = create_env_config() + print(f"\n✓ 配置创建成功") + print(f" AUTH_BASE: {config.auth_base}") + print(f" CLIENT_KEY: {config.client_key[:10]}..." if config.client_key else " CLIENT_KEY: ") + + # 步骤 4: 调用 API + print(f"\n【测试】获取访问令牌...") + try: + response = request_api_with_auto_refresh( + method="POST", + url=f"{config.auth_base}/auth/skill-credit/session", + dry_run=False, + config=config, + body={"clientKey": config.client_key} if config.client_key else {}, + ) + print(f"✓ 响应状态:{response.status}") + if response.status == 200: + print(f" 响应体:{response.body[:100]}...") + else: + print(f" 响应体:{response.body}") + except Exception as e: + print(f"❌ 失败:{e}") + + print("\n" + "=" * 60) + +if __name__ == "__main__": + main() diff --git a/python_auth_runtime/scripts/load_env.py b/python_auth_runtime/scripts/load_env.py new file mode 100644 index 0000000..900362b --- /dev/null +++ b/python_auth_runtime/scripts/load_env.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +加载 .env.local 或 .env 文件到环境变量 + +在具体 skill 中使用: + from load_env import load_env + load_env() # 加载 .env.local 或 .env + +参考:~/clawd/skills/1688-product-master/scripts/run.ts 中的 loadEnvLocal() +""" + +import os +from pathlib import Path + + +def load_env(env_path: Path | None = None) -> bool: + """ + 加载 .env.local 或 .env 文件到环境变量 + + 优先级: + 1. 指定的 env_path + 2. 当前目录的 .env.local + 3. 当前目录的 .env + 4. 父目录的 .env.local + 5. 父目录的 .env + + Returns: + bool: 是否成功加载 + """ + if env_path is None: + # 查找 .env 文件 + possible_paths = [ + Path.cwd() / ".env.local", + Path.cwd() / ".env", + Path.cwd().parent / ".env.local", + Path.cwd().parent / ".env", + ] + for p in possible_paths: + if p.exists(): + env_path = p + break + + if env_path is None or not env_path.exists(): + return False + + print(f"📄 加载环境变量:{env_path}") + + with open(env_path, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + # 跳过空行和注释 + if not line or line.startswith("#"): + continue + + # 解析 KEY=VALUE + eq_index = line.find("=") + if eq_index <= 0: + continue + + key = line[:eq_index].strip() + value = line[eq_index + 1:].strip() + + # 去除引号 + if (value.startswith('"') and value.endswith('"')) or \ + (value.startswith("'") and value.endswith("'")): + value = value[1:-1] + + # 只设置尚未存在的环境变量(命令行参数优先级更高) + if key not in os.environ: + os.environ[key] = value + print(f" ✓ {key} = {value[:10]}..." if len(str(value)) > 10 else f" ✓ {key} = {value}") + + return True + + +if __name__ == "__main__": + import sys + + env_file = Path(sys.argv[1]) if len(sys.argv) > 1 else None + + if load_env(env_file): + print("\n✅ 环境变量加载成功") + print(f"\n当前环境变量:") + for key in ["CLIENT_KEY", "AUTH_BASE", "AUTH_CACHE_DIR"]: + value = os.getenv(key, "") + print(f" {key} = {value[:10]}..." if len(str(value)) > 10 else f" {key} = {value}") + else: + print("❌ 未找到 .env 文件") + sys.exit(1)