Coverage for aipyapp/aipy/diagnose.py: 22%

124 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-11 12:02 +0200

1#! /usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3 

4import sys 

5import json 

6import uuid 

7import time 

8import platform 

9import locale 

10import mimetypes 

11from io import BytesIO 

12 

13import requests 

14from loguru import logger 

15 

16from aipyapp import __version__ 

17from aipyapp.aipy.config import CONFIG_DIR 

18 

19CONFIG_FILE = CONFIG_DIR / '.diagnose.json' 

20UPDATE_INTERVAL = 8 * 3600 

21 

22class NoopDiagnose: 

23 def __getattr__(self, name): 

24 def noop(*args, **kwargs): 

25 pass 

26 return noop 

27 

28class Diagnose: 

29 def __init__(self, api_url, api_key): 

30 self._api_url = api_url 

31 self._api_key = api_key 

32 self.log = logger.bind(src='diagnose') 

33 self.load_config() 

34 

35 def save_config(self): 

36 CONFIG_FILE.write_text(json.dumps({'xid': self._xid, 'last_update': self._last_update})) 

37 

38 def load_config(self): 

39 if not CONFIG_FILE.exists(): 

40 self._xid = uuid.uuid4().hex 

41 self._last_update = 0 

42 self.save_config() 

43 else: 

44 with CONFIG_FILE.open('r') as f: 

45 data = json.load(f) 

46 self._xid = data.get('xid') 

47 self._last_update = data.get('last_update') 

48 

49 @classmethod 

50 def create(cls, settings): 

51 config = settings.get('diagnose') 

52 if config: 

53 api_key = config.get('api_key') 

54 api_url = config.get('api_url') 

55 enabled = config.get('enabled', True) 

56 if not api_url: 

57 enabled = False 

58 else: 

59 enabled = False 

60 

61 return cls(api_url, api_key) if enabled else NoopDiagnose() 

62 

63 def get_meta(self): 

64 return { 

65 "system": platform.system(), 

66 "version": platform.version(), 

67 "platform": platform.platform(), 

68 "arch": platform.machine(), 

69 "python": sys.executable, 

70 "python_version": sys.version, 

71 "python_base_prefix": sys.base_prefix, 

72 "locale": locale.getlocale(), 

73 } 

74 

75 def check_update(self, force=False): 

76 if not force and int(time.time()) - self._last_update < UPDATE_INTERVAL: 

77 return {} 

78 self._last_update = int(time.time()) 

79 self.save_config() 

80 

81 data = { 

82 "xid": self._xid, 

83 "version": __version__, 

84 "meta": self.get_meta() 

85 } 

86 headers = { 

87 "Content-Type": "application/json", 

88 #"API-KEY": self._api_key 

89 } 

90 

91 try: 

92 response = requests.post( 

93 f"{self._api_url}/4b16535b7e6147f2861508a2ad5f5ce8", 

94 json=data, 

95 headers=headers 

96 ) 

97 if response.status_code == 200: 

98 result = response.json() 

99 if result.get("success", False): 

100 return { 

101 "has_update": result.get("has_update", False), 

102 "latest_version": result.get("latest_version", "unknown"), 

103 "current_version": __version__, 

104 } 

105 else: 

106 self.log.error(f"Server error: {result.get('error', '未知错误')}") 

107 else: 

108 self.log.error(f"Request failed: HTTP {response.status_code}") 

109 

110 except Exception as e: 

111 self.log.error(f"Connection error: {str(e)}") 

112 return {"error": "版本检查失败"} 

113 

114 def report_data(self, data, filename): 

115 try: 

116 # 确保数据是字符串格式 

117 if isinstance(data, (dict, list)): 

118 data = json.dumps(data, ensure_ascii=False, indent=4) 

119 elif not isinstance(data, str): 

120 data = str(data) 

121 

122 # 创建文件对象 

123 file_data = BytesIO(data.encode('utf-8')) 

124 file_data.seek(0) # 确保文件指针在开始位置 

125 except Exception as e: 

126 self.log.error(f"Failed to prepare data: {str(e)}") 

127 return {'success': False, 'error': str(e)} 

128 

129 headers = {'API-KEY': self._api_key} 

130 content_type, _ = mimetypes.guess_type(filename) 

131 if not content_type: 

132 content_type = 'application/octet-stream' 

133 

134 files = { 

135 'file': ( 

136 filename, 

137 file_data, 

138 content_type 

139 ) 

140 } 

141 

142 error = 'Unknown error' 

143 try: 

144 response = requests.post(f"{self._api_url}/a6477529c8c34b6a8ca4bc2d7253ab76", files=files, headers=headers) 

145 if 200 <= response.status_code < 300: 

146 try: 

147 result = response.json() 

148 if result.get('success') and 'viewUrl' in result: 

149 self.log.info(f"Report uploaded successfully. View URL: {result['viewUrl']}") 

150 return {'success': True, 'url': result['viewUrl']} 

151 else: 

152 self.log.error(f"Upload failed: {result.get('error', 'Unknown error')}") 

153 except json.JSONDecodeError: 

154 error = "Failed to parse response as JSON" 

155 self.log.error(error) 

156 else: 

157 error = f"Upload failed with status code: {response.status_code}" 

158 self.log.error(error) 

159 

160 except Exception as e: 

161 error = f"Failed to upload report: {str(e)}" 

162 self.log.error(error) 

163 return {'success': False, 'error': error} 

164 

165 def report_code_error(self, history): 

166 # Report code execution errors from history 

167 # Each history entry contains code and execution result 

168 # We only collect entries with traceback information 

169 # Returns True if report was sent successfully 

170 if not self._api_key: 

171 return True 

172 

173 data = [] 

174 for h in history: 

175 result = h.get('result') 

176 if not result: 

177 continue 

178 traceback = result.get('traceback') 

179 if not traceback: 

180 continue 

181 data.append({ 

182 'code': h.get('code'), 

183 'traceback': traceback, 

184 'error': result.get('errstr') 

185 }) 

186 

187 if data: 

188 return self.report_data(data, 'code_error.json') 

189 return True 

190 

191if __name__ == '__main__': 

192 settings = { 

193 'diagnose': { 

194 'api_key': 'sk-aipy-', 

195 'api_url': 'https://aipy.xxyy.eu.org/', 

196 } 

197 } 

198 diagnose = Diagnose.create(settings) 

199 update = diagnose.check_update() 

200 print(update) 

201 url = diagnose.report_code_error([ 

202 {'code': 'print("Hello, World!")', 'result': {'traceback': 'Traceback (most recent call last):\n File "test.py", line 1, in <module>\n print("Hello, World!")\nNameError: name \'print\' is not defined\n', 'errstr': 'NameError: name \'print\' is not defined'}} 

203 ]) 

204 print(url)