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

1#! /usr/bin/env python3

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

3

4 import sys

5 import json

6 import uuid

7 import time

8 import platform

9 import locale

10 import mimetypes

11 from io import BytesIO

12

13 import requests

14 from loguru import logger

15

16 from aipyapp import __version__

17 from aipyapp.aipy.config import CONFIG_DIR

18

19 CONFIG_FILE = CONFIG_DIR / '.diagnose.json'

20 UPDATE_INTERVAL = 8 * 3600

21

22 class NoopDiagnose:

23 def __getattr__(self, name):

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

25 pass

26 return noop

27

28 class 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

191 if __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)