Skip to main content
Glama
refactored_client.py18.5 kB
#!/usr/bin/env python3 """ Torna 客户端 - 参考 Java SDK 重新设计 """ import json import os import urllib.parse from abc import ABC, abstractmethod from datetime import datetime from typing import Any, Dict, List, Optional, Type, TypeVar, Generic import httpx # 类型变量定义 T = TypeVar('T', bound='BaseResponse') class TornaConfig: """配置常量类 - 参考 Java SDK 的 OpenConfig""" SUCCESS_CODE = "0" DEFAULT_VERSION = "1.0" API_NAME = "name" DATA_NAME = "data" VERSION_NAME = "version" TIMESTAMP_NAME = "timestamp" TIMESTAMP_PATTERN = "%Y-%m-%d %H:%M:%S" ACCESS_TOKEN_NAME = "access_token" LOCALE = "zh-CN" CONNECT_TIMEOUT = 60 READ_TIMEOUT = 60 class TornaAPIError(Exception): """API 错误异常类""" def __init__(self, code: str, message: str): self.code = code self.message = message super().__init__(f"Torna API Error {code}: {message}") class BaseResponse(ABC): """响应基类 - 参考 Java SDK 的 BaseResponse""" def __init__(self): self.code: Optional[str] = None self.msg: Optional[str] = None self.data: Optional[Any] = None def is_success(self) -> bool: """检查是否成功""" return TornaConfig.SUCCESS_CODE == self.code @classmethod def from_dict(cls: Type[T], data: Dict[str, Any]) -> T: """从字典创建响应对象""" response = cls() response.code = data.get("code") response.msg = data.get("msg") response.data = data.get("data") return response def to_dict(self) -> Dict[str, Any]: """转换为字典""" return { "code": self.code, "msg": self.msg, "data": self.data } class DocListResponse(BaseResponse): """文档列表响应类""" def __init__(self): super().__init__() self.data: Optional[List[Dict[str, Any]]] = None class DocGetResponse(BaseResponse): """文档详情响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class DocPushResponse(BaseResponse): """文档推送响应类""" def __init__(self): super().__init__() self.data: Optional[List[Dict[str, Any]]] = None class ModuleGetResponse(BaseResponse): """模块信息响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class DocDetailsResponse(BaseResponse): """批量文档详情响应类""" def __init__(self): super().__init__() self.data: Optional[List[Dict[str, Any]]] = None class DocCategoryCreateResponse(BaseResponse): """创建分类响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class DocCategoryListResponse(BaseResponse): """分类列表响应类""" def __init__(self): super().__init__() self.data: Optional[List[Dict[str, Any]]] = None class DocCategoryNameUpdateResponse(BaseResponse): """更新分类名称响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class EnumPushResponse(BaseResponse): """枚举推送响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class ModuleDebugEnvSetResponse(BaseResponse): """设置调试环境响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class ModuleDebugEnvDeleteResponse(BaseResponse): """删除调试环境响应类""" def __init__(self): super().__init__() self.data: Optional[Dict[str, Any]] = None class RequestForm: """请求表单类 - 参考 Java SDK 的 RequestForm""" def __init__(self, form_data: Dict[str, Any]): self.form: Dict[str, Any] = form_data.copy() def get_form(self) -> Dict[str, Any]: """获取表单数据""" return self.form.copy() class BaseRequest(ABC, Generic[T]): """请求基类 - 参考 Java SDK 的 BaseRequest""" def __init__(self, token: str, response_class: Type[T]): self.token = token self.response_class: Type[T] = response_class @abstractmethod def name(self) -> str: """接口名称""" pass @abstractmethod def version(self) -> str: """接口版本""" pass def create_request_form(self) -> RequestForm: """创建请求表单 - 模板方法""" data = self.build_json_data() # 构建公共参数 param = { TornaConfig.API_NAME: self.name(), TornaConfig.DATA_NAME: self._url_encode(data), TornaConfig.VERSION_NAME: self.version(), TornaConfig.TIMESTAMP_NAME: datetime.now().strftime(TornaConfig.TIMESTAMP_PATTERN), TornaConfig.ACCESS_TOKEN_NAME: self.token, } return RequestForm(param) def build_json_data(self) -> str: """构建 JSON 数据""" # 子类可以重写此方法来添加特定参数 return "{}" def parse_response(self, response_text: str) -> T: """解析响应""" try: response_data = json.loads(response_text) return self.response_class.from_dict(response_data) except (json.JSONDecodeError, TypeError) as e: raise TornaAPIError("PARSE_ERROR", f"响应解析失败: {e}") def _url_encode(self, data: str) -> str: """URL 编码""" return urllib.parse.quote(data, safe='') class DocListRequest(BaseRequest[DocListResponse]): """文档列表请求类 - 参考 Java SDK 的 DocListRequest""" def __init__(self, token: str): super().__init__(token, DocListResponse) def name(self) -> str: return "doc.list" def version(self) -> str: return "1.0" def create_request_form(self) -> RequestForm: """doc.list 不需要额外参数,返回空对象""" return super().create_request_form() class DocGetRequest(BaseRequest[DocGetResponse]): """文档详情请求类""" def __init__(self, token: str, doc_id: str): super().__init__(token, DocGetResponse) self.doc_id = doc_id def name(self) -> str: return "doc.detail" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建包含文档ID的JSON数据""" return json.dumps({"id": self.doc_id}) class DocPushRequest(BaseRequest[DocPushResponse]): """文档推送请求类""" def __init__(self, token: str): super().__init__(token, DocPushResponse) self.apis: Optional[List[Dict[str, Any]]] = None self.debug_envs: Optional[List[Dict[str, Any]]] = None def name(self) -> str: return "doc.push" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建推送数据的JSON""" data = {} if self.apis: data["apis"] = self.apis if self.debug_envs: data["debugEnvs"] = self.debug_envs return json.dumps(data, ensure_ascii=False) def set_apis(self, apis: List[Dict[str, Any]]) -> 'DocPushRequest': """设置API列表""" self.apis = apis return self def set_debug_envs(self, debug_envs: List[Dict[str, Any]]) -> 'DocPushRequest': """设置调试环境""" self.debug_envs = debug_envs return self class ModuleGetRequest(BaseRequest[ModuleGetResponse]): """模块信息请求类""" def __init__(self, token: str): super().__init__(token, ModuleGetResponse) def name(self) -> str: return "module.get" def version(self) -> str: return "1.0" class DocDetailsRequest(BaseRequest[DocDetailsResponse]): """批量文档详情请求类""" def __init__(self, token: str, doc_ids: List[str]): super().__init__(token, DocDetailsResponse) self.doc_ids = doc_ids def name(self) -> str: return "doc.details" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建文档ID列表的JSON""" return json.dumps({"ids": self.doc_ids}) class TornaClient: """Torna 客户端类 - 参考 Java SDK 的 OpenClient""" def __init__(self, base_url: str, token: str): # 处理基础URL,确保正确格式 base_url = base_url.rstrip('/') # 如果URL中已经有/api路径,则不重复添加 # 使用更精确的匹配:检查是否有/api路径而不是简单包含 if not base_url.endswith('/api') and '/api/' not in base_url: base_url = base_url + '/api' self.base_url = base_url self.token = token self.client: Optional[httpx.Client] = None def __enter__(self): """上下文管理器入口""" self.client = httpx.Client(timeout=60.0) return self def __exit__(self, exc_type, exc_val, exc_tb): """上下文管理器出口""" if self.client: self.client.close() def execute(self, request: BaseRequest) -> BaseResponse: """执行请求 - 核心方法""" if not self.client: raise TornaAPIError("CLIENT_ERROR", "客户端未初始化,请使用 with 语句") # 创建请求表单 request_form = request.create_request_form() # 构建请求头 headers = self._build_headers() try: # 发送HTTP请求 response = self.client.post( self.base_url, json=request_form.get_form(), headers=headers, timeout=60.0 ) response.raise_for_status() # 解析响应 response_text = response.text return request.parse_response(response_text) except httpx.HTTPStatusError as e: raise TornaAPIError("HTTP_ERROR", f"HTTP请求失败: {e.response.status_code} - {e.response.text}") except httpx.TimeoutException as e: raise TornaAPIError("TIMEOUT_ERROR", f"请求超时: {e}") except Exception as e: raise TornaAPIError("UNKNOWN_ERROR", f"未知错误: {e}") def _build_headers(self) -> Dict[str, str]: """构建请求头""" return { "Accept-Language": TornaConfig.LOCALE, "Content-Type": "application/json" } # 便捷方法 def get_documents(self) -> List[Dict[str, Any]]: """获取文档列表""" request = DocListRequest(self.token) response = self.execute(request) if not response.is_success(): raise TornaAPIError(response.code or "UNKNOWN", response.msg or "获取文档失败") return response.data or [] def get_document(self, doc_id: str) -> Dict[str, Any]: """获取单个文档详情""" request = DocGetRequest(self.token, doc_id) response = self.execute(request) if not response.is_success(): raise TornaAPIError(response.code or "UNKNOWN", response.msg or f"获取文档 {doc_id} 失败") return response.data def get_module_info(self) -> Dict[str, Any]: """获取模块信息""" request = ModuleGetRequest(self.token) response = self.execute(request) if not response.is_success(): raise TornaAPIError(response.code or "UNKNOWN", response.msg or "获取模块信息失败") return response.data def push_document(self, doc_config: Dict[str, Any]) -> List[Dict[str, Any]]: """推送单个文档""" request = DocPushRequest(self.token) request.set_apis([doc_config]) response = self.execute(request) if not response.is_success(): raise TornaAPIError(response.code or "UNKNOWN", response.msg or "推送文档失败") return response.data or [] def push_documents(self, docs: List[Dict[str, Any]], debug_envs: Optional[List[Dict[str, Any]]] = None) -> List[Dict[str, Any]]: """批量推送文档""" request = DocPushRequest(self.token) request.set_apis(docs) if debug_envs: request.set_debug_envs(debug_envs) response = self.execute(request) if not response.is_success(): raise TornaAPIError(response.code or "UNKNOWN", response.msg or "批量推送文档失败") return response.data or [] def get_batch_documents(self, doc_ids: List[str]) -> List[Dict[str, Any]]: """批量获取文档详情""" request = DocDetailsRequest(self.token, doc_ids) response = self.execute(request) if not response.is_success(): raise TornaAPIError(response.code or "UNKNOWN", response.msg or "批量获取文档失败") return response.data or [] # 使用示例 def example_usage(): """使用示例""" # 1. 基本使用(推荐) with TornaClient("http://localhost:7700", "your-token") as client: try: # 获取所有文档 docs = client.get_documents() print(f"找到 {len(docs)} 个文档") # 获取模块信息 module_info = client.get_module_info() print(f"模块信息: {module_info}") # 推送单个文档 doc_config = { "name": "用户登录", "description": "用户登录接口", "url": "/api/user/login", "httpMethod": "POST", "contentType": "application/json" } result = client.push_document(doc_config) print(f"推送结果: {result}") except TornaAPIError as e: print(f"API错误: {e}") # 2. 手动请求 client = TornaClient("http://localhost:7700", "your-token") # 创建并执行请求 request = DocListRequest("your-token") with client: response = client.execute(request) print(f"响应: {response.to_dict()}") # ==================== 新增的 Request 类 - 补充 Java SDK 所有功能 ==================== class DocCategoryCreateRequest(BaseRequest[DocCategoryCreateResponse]): """创建分类请求类""" def __init__(self, token: str, name: str): super().__init__(token, DocCategoryCreateResponse) self.name = name def name(self) -> str: return "doc.category.create" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建分类创建数据""" return json.dumps({"name": self.name}) class DocCategoryListRequest(BaseRequest[DocCategoryListResponse]): """分类列表请求类""" def __init__(self, token: str): super().__init__(token, DocCategoryListResponse) def name(self) -> str: return "doc.category.list" def version(self) -> str: return "1.0" class DocCategoryNameUpdateRequest(BaseRequest[DocCategoryNameUpdateResponse]): """更新分类名称请求类""" def __init__(self, token: str, category_id: str, name: str): super().__init__(token, DocCategoryNameUpdateResponse) self.category_id = category_id self.name = name def name(self) -> str: return "doc.category.name.update" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建分类名称更新数据""" return json.dumps({"id": self.category_id, "name": self.name}) class EnumPushRequest(BaseRequest[EnumPushResponse]): """枚举推送请求类""" def __init__(self, token: str, enum_name: str, description: str = "", items: Optional[List[Dict[str, Any]]] = None): super().__init__(token, EnumPushResponse) self.enum_name = enum_name self.description = description self.items = items or [] def name(self) -> str: return "enum.push" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建枚举推送数据""" data = { "name": self.enum_name, "description": self.description, "items": self.items } return json.dumps(data, ensure_ascii=False) class EnumBatchPushRequest(BaseRequest[EnumPushResponse]): """批量枚举推送请求类""" def __init__(self, token: str, enums: List[Dict[str, Any]]): super().__init__(token, EnumPushResponse) self.enums = enums def name(self) -> str: return "enum.batch.push" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建批量枚举推送数据""" return json.dumps({"enums": self.enums}, ensure_ascii=False) class ModuleDebugEnvSetRequest(BaseRequest[ModuleDebugEnvSetResponse]): """设置模块调试环境请求类""" def __init__(self, token: str, name: str, url: str): super().__init__(token, ModuleDebugEnvSetResponse) self.name = name self.url = url def name(self) -> str: return "module.debug.env.set" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建调试环境设置数据""" return json.dumps({"name": self.name, "url": self.url}) class ModuleDebugEnvDeleteRequest(BaseRequest[ModuleDebugEnvDeleteResponse]): """删除模块调试环境请求类""" def __init__(self, token: str, name: str): super().__init__(token, ModuleDebugEnvDeleteResponse) self.name = name def name(self) -> str: return "module.debug.env.delete" def version(self) -> str: return "1.0" def build_json_data(self) -> str: """构建调试环境删除数据""" return json.dumps({"name": self.name}) if __name__ == "__main__": example_usage()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/li7hai26/torna-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server