Fix the Error "'ReportTask' object has no attribute 'ir_file_path'" When Exporti…
…ng PDF and Ensure that Pango Dependencies are not Missing
Showing
3 changed files
with
104 additions
and
2 deletions
| @@ -9,7 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ | @@ -9,7 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ | ||
| 9 | PATH="/root/.local/bin:${PATH}" \ | 9 | PATH="/root/.local/bin:${PATH}" \ |
| 10 | PLAYWRIGHT_BROWSERS_PATH=/ms-playwright | 10 | PLAYWRIGHT_BROWSERS_PATH=/ms-playwright |
| 11 | 11 | ||
| 12 | -# Install system dependencies required by scientific Python stack, Playwright, and Streamlit | 12 | +# Install system dependencies required by scientific Python stack, Playwright, Streamlit, and WeasyPrint PDF |
| 13 | RUN apt-get update && apt-get install -y --no-install-recommends \ | 13 | RUN apt-get update && apt-get install -y --no-install-recommends \ |
| 14 | build-essential \ | 14 | build-essential \ |
| 15 | curl \ | 15 | curl \ |
| @@ -19,6 +19,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ | @@ -19,6 +19,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| 19 | libgtk-3-0 \ | 19 | libgtk-3-0 \ |
| 20 | libpango-1.0-0 \ | 20 | libpango-1.0-0 \ |
| 21 | libpangocairo-1.0-0 \ | 21 | libpangocairo-1.0-0 \ |
| 22 | + libpangoft2-1.0-0 \ | ||
| 23 | + libgdk-pixbuf2.0-0 \ | ||
| 24 | + libffi-dev \ | ||
| 25 | + libcairo2 \ | ||
| 22 | libatk1.0-0 \ | 26 | libatk1.0-0 \ |
| 23 | libatk-bridge2.0-0 \ | 27 | libatk-bridge2.0-0 \ |
| 24 | libxcb1 \ | 28 | libxcb1 \ |
| @@ -160,6 +160,14 @@ def initialize_report_engine(): | @@ -160,6 +160,14 @@ def initialize_report_engine(): | ||
| 160 | try: | 160 | try: |
| 161 | report_agent = create_agent() | 161 | report_agent = create_agent() |
| 162 | logger.info("Report Engine初始化成功") | 162 | logger.info("Report Engine初始化成功") |
| 163 | + | ||
| 164 | + # 检测 PDF 生成依赖(Pango) | ||
| 165 | + try: | ||
| 166 | + from .utils.dependency_check import log_dependency_status | ||
| 167 | + log_dependency_status() | ||
| 168 | + except Exception as dep_err: | ||
| 169 | + logger.warning(f"依赖检测失败: {dep_err}") | ||
| 170 | + | ||
| 163 | return True | 171 | return True |
| 164 | except Exception as e: | 172 | except Exception as e: |
| 165 | logger.exception(f"Report Engine初始化失败: {str(e)}") | 173 | logger.exception(f"Report Engine初始化失败: {str(e)}") |
| @@ -198,6 +206,8 @@ class ReportTask: | @@ -198,6 +206,8 @@ class ReportTask: | ||
| 198 | self.report_file_name = "" | 206 | self.report_file_name = "" |
| 199 | self.state_file_path = "" | 207 | self.state_file_path = "" |
| 200 | self.state_file_relative_path = "" | 208 | self.state_file_relative_path = "" |
| 209 | + self.ir_file_path = "" | ||
| 210 | + self.ir_file_relative_path = "" | ||
| 201 | # ====== 流式事件缓存与并发保护 ====== | 211 | # ====== 流式事件缓存与并发保护 ====== |
| 202 | # 使用deque保存最近的事件,结合锁保证多线程下的安全访问 | 212 | # 使用deque保存最近的事件,结合锁保证多线程下的安全访问 |
| 203 | self.event_history: deque = deque(maxlen=1000) | 213 | self.event_history: deque = deque(maxlen=1000) |
| @@ -248,7 +258,9 @@ class ReportTask: | @@ -248,7 +258,9 @@ class ReportTask: | ||
| 248 | 'report_file_name': self.report_file_name, | 258 | 'report_file_name': self.report_file_name, |
| 249 | 'report_file_path': self.report_file_relative_path or self.report_file_path, | 259 | 'report_file_path': self.report_file_relative_path or self.report_file_path, |
| 250 | 'state_file_ready': bool(self.state_file_path), | 260 | 'state_file_ready': bool(self.state_file_path), |
| 251 | - 'state_file_path': self.state_file_relative_path or self.state_file_path | 261 | + 'state_file_path': self.state_file_relative_path or self.state_file_path, |
| 262 | + 'ir_file_ready': bool(self.ir_file_path), | ||
| 263 | + 'ir_file_path': self.ir_file_relative_path or self.ir_file_path | ||
| 252 | } | 264 | } |
| 253 | 265 | ||
| 254 | def publish_event(self, event_type: str, payload: Dict[str, Any]) -> None: | 266 | def publish_event(self, event_type: str, payload: Dict[str, Any]) -> None: |
| @@ -431,6 +443,8 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = " | @@ -431,6 +443,8 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = " | ||
| 431 | task.report_file_name = generation_result.get('report_filename', '') | 443 | task.report_file_name = generation_result.get('report_filename', '') |
| 432 | task.state_file_path = generation_result.get('state_filepath', '') | 444 | task.state_file_path = generation_result.get('state_filepath', '') |
| 433 | task.state_file_relative_path = generation_result.get('state_relative_path', '') | 445 | task.state_file_relative_path = generation_result.get('state_relative_path', '') |
| 446 | + task.ir_file_path = generation_result.get('ir_filepath', '') | ||
| 447 | + task.ir_file_relative_path = generation_result.get('ir_relative_path', '') | ||
| 434 | task.publish_event('html_ready', { | 448 | task.publish_event('html_ready', { |
| 435 | 'message': 'HTML渲染完成,可刷新预览', | 449 | 'message': 'HTML渲染完成,可刷新预览', |
| 436 | 'report_file': task.report_file_relative_path or task.report_file_path, | 450 | 'report_file': task.report_file_relative_path or task.report_file_path, |
| @@ -1027,6 +1041,17 @@ def export_pdf(task_id: str): | @@ -1027,6 +1041,17 @@ def export_pdf(task_id: str): | ||
| 1027 | Response: PDF文件流或错误信息 | 1041 | Response: PDF文件流或错误信息 |
| 1028 | """ | 1042 | """ |
| 1029 | try: | 1043 | try: |
| 1044 | + # 检测 Pango 依赖 | ||
| 1045 | + from .utils.dependency_check import check_pango_available | ||
| 1046 | + pango_available, pango_message = check_pango_available() | ||
| 1047 | + if not pango_available: | ||
| 1048 | + return jsonify({ | ||
| 1049 | + 'success': False, | ||
| 1050 | + 'error': 'PDF 导出功能不可用:缺少 Pango 系统依赖', | ||
| 1051 | + 'details': '请查看 requirements.txt 文件中的 "===== PDF生成 =====" 部分了解如何安装 Pango', | ||
| 1052 | + 'system_message': pango_message | ||
| 1053 | + }), 503 | ||
| 1054 | + | ||
| 1030 | # 获取任务信息 | 1055 | # 获取任务信息 |
| 1031 | task = tasks_registry.get(task_id) | 1056 | task = tasks_registry.get(task_id) |
| 1032 | if not task: | 1057 | if not task: |
| @@ -1104,6 +1129,17 @@ def export_pdf_from_ir(): | @@ -1104,6 +1129,17 @@ def export_pdf_from_ir(): | ||
| 1104 | Response: PDF文件流或错误信息 | 1129 | Response: PDF文件流或错误信息 |
| 1105 | """ | 1130 | """ |
| 1106 | try: | 1131 | try: |
| 1132 | + # 检测 Pango 依赖 | ||
| 1133 | + from .utils.dependency_check import check_pango_available | ||
| 1134 | + pango_available, pango_message = check_pango_available() | ||
| 1135 | + if not pango_available: | ||
| 1136 | + return jsonify({ | ||
| 1137 | + 'success': False, | ||
| 1138 | + 'error': 'PDF 导出功能不可用:缺少 Pango 系统依赖', | ||
| 1139 | + 'details': '请查看 requirements.txt 文件中的 "===== PDF生成 =====" 部分了解如何安装 Pango', | ||
| 1140 | + 'system_message': pango_message | ||
| 1141 | + }), 503 | ||
| 1142 | + | ||
| 1107 | data = request.get_json() | 1143 | data = request.get_json() |
| 1108 | 1144 | ||
| 1109 | if not data or 'document_ir' not in data: | 1145 | if not data or 'document_ir' not in data: |
ReportEngine/utils/dependency_check.py
0 → 100644
| 1 | +""" | ||
| 2 | +检测系统依赖工具 | ||
| 3 | +用于检测 PDF 生成所需的系统依赖 | ||
| 4 | +""" | ||
| 5 | +import sys | ||
| 6 | +from loguru import logger | ||
| 7 | + | ||
| 8 | + | ||
| 9 | +def check_pango_available(): | ||
| 10 | + """ | ||
| 11 | + 检测 Pango 库是否可用 | ||
| 12 | + | ||
| 13 | + Returns: | ||
| 14 | + tuple: (is_available: bool, message: str) | ||
| 15 | + """ | ||
| 16 | + try: | ||
| 17 | + # 尝试导入 weasyprint 并初始化 Pango | ||
| 18 | + from weasyprint import HTML | ||
| 19 | + from weasyprint.text.ffi import ffi, pango | ||
| 20 | + | ||
| 21 | + # 尝试调用 Pango 函数来确认库可用 | ||
| 22 | + pango.pango_version() | ||
| 23 | + | ||
| 24 | + return True, "✓ Pango 依赖检测通过,PDF 导出功能可用" | ||
| 25 | + except OSError as e: | ||
| 26 | + # Pango 库未安装或无法加载 | ||
| 27 | + error_msg = str(e) | ||
| 28 | + if 'pango' in error_msg.lower(): | ||
| 29 | + return False, ( | ||
| 30 | + "⚠ Pango 依赖未安装或无法加载,PDF 导出功能将不可用(其他功能不受影响)\n" | ||
| 31 | + " 请查看 requirements.txt 文件中的 PDF 生成部分,了解如何安装 Pango 依赖" | ||
| 32 | + ) | ||
| 33 | + return False, f"⚠ PDF 依赖加载失败: {error_msg}" | ||
| 34 | + except ImportError as e: | ||
| 35 | + # weasyprint 未安装 | ||
| 36 | + return False, f"⚠ WeasyPrint 未安装: {e}" | ||
| 37 | + except Exception as e: | ||
| 38 | + # 其他未知错误 | ||
| 39 | + return False, f"⚠ PDF 依赖检测失败: {e}" | ||
| 40 | + | ||
| 41 | + | ||
| 42 | +def log_dependency_status(): | ||
| 43 | + """ | ||
| 44 | + 记录系统依赖状态到日志 | ||
| 45 | + """ | ||
| 46 | + is_available, message = check_pango_available() | ||
| 47 | + | ||
| 48 | + if is_available: | ||
| 49 | + logger.success(message) | ||
| 50 | + else: | ||
| 51 | + logger.warning(message) | ||
| 52 | + logger.info("提示:PDF 导出功能需要 Pango 库支持,但不影响系统其他功能的正常使用") | ||
| 53 | + logger.info("安装说明请参考:requirements.txt 文件中的 '===== PDF生成 =====' 部分") | ||
| 54 | + | ||
| 55 | + return is_available | ||
| 56 | + | ||
| 57 | + | ||
| 58 | +if __name__ == "__main__": | ||
| 59 | + # 用于独立测试 | ||
| 60 | + is_available, message = check_pango_available() | ||
| 61 | + print(message) | ||
| 62 | + sys.exit(0 if is_available else 1) |
-
Please register or login to post a comment