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
« 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 -*-
4import sys
5import json
6import uuid
7import time
8import platform
9import locale
10import mimetypes
11from io import BytesIO
13import requests
14from loguru import logger
16from aipyapp import __version__
17from aipyapp.aipy.config import CONFIG_DIR
19CONFIG_FILE = CONFIG_DIR / '.diagnose.json'
20UPDATE_INTERVAL = 8 * 3600
22class NoopDiagnose:
23 def __getattr__(self, name):
24 def noop(*args, **kwargs):
25 pass
26 return noop
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()
35 def save_config(self):
36 CONFIG_FILE.write_text(json.dumps({'xid': self._xid, 'last_update': self._last_update}))
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')
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
61 return cls(api_url, api_key) if enabled else NoopDiagnose()
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 }
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()
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 }
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}")
110 except Exception as e:
111 self.log.error(f"Connection error: {str(e)}")
112 return {"error": "版本检查失败"}
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)
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)}
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'
134 files = {
135 'file': (
136 filename,
137 file_data,
138 content_type
139 )
140 }
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)
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}
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
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 })
187 if data:
188 return self.report_data(data, 'code_error.json')
189 return True
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)