Fix the Error "'ReportTask' object has no attribute 'ir_file_path'" When Exporting PDF and Ensure that Pango Dependencies are not Missing
This commit is contained in:
+5
-1
@@ -9,7 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
|
|||||||
PATH="/root/.local/bin:${PATH}" \
|
PATH="/root/.local/bin:${PATH}" \
|
||||||
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
||||||
|
|
||||||
# Install system dependencies required by scientific Python stack, Playwright, and Streamlit
|
# Install system dependencies required by scientific Python stack, Playwright, Streamlit, and WeasyPrint PDF
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
build-essential \
|
build-essential \
|
||||||
curl \
|
curl \
|
||||||
@@ -19,6 +19,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
libgtk-3-0 \
|
libgtk-3-0 \
|
||||||
libpango-1.0-0 \
|
libpango-1.0-0 \
|
||||||
libpangocairo-1.0-0 \
|
libpangocairo-1.0-0 \
|
||||||
|
libpangoft2-1.0-0 \
|
||||||
|
libgdk-pixbuf2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
libcairo2 \
|
||||||
libatk1.0-0 \
|
libatk1.0-0 \
|
||||||
libatk-bridge2.0-0 \
|
libatk-bridge2.0-0 \
|
||||||
libxcb1 \
|
libxcb1 \
|
||||||
|
|||||||
@@ -160,6 +160,14 @@ def initialize_report_engine():
|
|||||||
try:
|
try:
|
||||||
report_agent = create_agent()
|
report_agent = create_agent()
|
||||||
logger.info("Report Engine初始化成功")
|
logger.info("Report Engine初始化成功")
|
||||||
|
|
||||||
|
# 检测 PDF 生成依赖(Pango)
|
||||||
|
try:
|
||||||
|
from .utils.dependency_check import log_dependency_status
|
||||||
|
log_dependency_status()
|
||||||
|
except Exception as dep_err:
|
||||||
|
logger.warning(f"依赖检测失败: {dep_err}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Report Engine初始化失败: {str(e)}")
|
logger.exception(f"Report Engine初始化失败: {str(e)}")
|
||||||
@@ -198,6 +206,8 @@ class ReportTask:
|
|||||||
self.report_file_name = ""
|
self.report_file_name = ""
|
||||||
self.state_file_path = ""
|
self.state_file_path = ""
|
||||||
self.state_file_relative_path = ""
|
self.state_file_relative_path = ""
|
||||||
|
self.ir_file_path = ""
|
||||||
|
self.ir_file_relative_path = ""
|
||||||
# ====== 流式事件缓存与并发保护 ======
|
# ====== 流式事件缓存与并发保护 ======
|
||||||
# 使用deque保存最近的事件,结合锁保证多线程下的安全访问
|
# 使用deque保存最近的事件,结合锁保证多线程下的安全访问
|
||||||
self.event_history: deque = deque(maxlen=1000)
|
self.event_history: deque = deque(maxlen=1000)
|
||||||
@@ -248,7 +258,9 @@ class ReportTask:
|
|||||||
'report_file_name': self.report_file_name,
|
'report_file_name': self.report_file_name,
|
||||||
'report_file_path': self.report_file_relative_path or self.report_file_path,
|
'report_file_path': self.report_file_relative_path or self.report_file_path,
|
||||||
'state_file_ready': bool(self.state_file_path),
|
'state_file_ready': bool(self.state_file_path),
|
||||||
'state_file_path': self.state_file_relative_path or self.state_file_path
|
'state_file_path': self.state_file_relative_path or self.state_file_path,
|
||||||
|
'ir_file_ready': bool(self.ir_file_path),
|
||||||
|
'ir_file_path': self.ir_file_relative_path or self.ir_file_path
|
||||||
}
|
}
|
||||||
|
|
||||||
def publish_event(self, event_type: str, payload: Dict[str, Any]) -> None:
|
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 = "
|
|||||||
task.report_file_name = generation_result.get('report_filename', '')
|
task.report_file_name = generation_result.get('report_filename', '')
|
||||||
task.state_file_path = generation_result.get('state_filepath', '')
|
task.state_file_path = generation_result.get('state_filepath', '')
|
||||||
task.state_file_relative_path = generation_result.get('state_relative_path', '')
|
task.state_file_relative_path = generation_result.get('state_relative_path', '')
|
||||||
|
task.ir_file_path = generation_result.get('ir_filepath', '')
|
||||||
|
task.ir_file_relative_path = generation_result.get('ir_relative_path', '')
|
||||||
task.publish_event('html_ready', {
|
task.publish_event('html_ready', {
|
||||||
'message': 'HTML渲染完成,可刷新预览',
|
'message': 'HTML渲染完成,可刷新预览',
|
||||||
'report_file': task.report_file_relative_path or task.report_file_path,
|
'report_file': task.report_file_relative_path or task.report_file_path,
|
||||||
@@ -1027,6 +1041,17 @@ def export_pdf(task_id: str):
|
|||||||
Response: PDF文件流或错误信息
|
Response: PDF文件流或错误信息
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 检测 Pango 依赖
|
||||||
|
from .utils.dependency_check import check_pango_available
|
||||||
|
pango_available, pango_message = check_pango_available()
|
||||||
|
if not pango_available:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'PDF 导出功能不可用:缺少 Pango 系统依赖',
|
||||||
|
'details': '请查看 requirements.txt 文件中的 "===== PDF生成 =====" 部分了解如何安装 Pango',
|
||||||
|
'system_message': pango_message
|
||||||
|
}), 503
|
||||||
|
|
||||||
# 获取任务信息
|
# 获取任务信息
|
||||||
task = tasks_registry.get(task_id)
|
task = tasks_registry.get(task_id)
|
||||||
if not task:
|
if not task:
|
||||||
@@ -1104,6 +1129,17 @@ def export_pdf_from_ir():
|
|||||||
Response: PDF文件流或错误信息
|
Response: PDF文件流或错误信息
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 检测 Pango 依赖
|
||||||
|
from .utils.dependency_check import check_pango_available
|
||||||
|
pango_available, pango_message = check_pango_available()
|
||||||
|
if not pango_available:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'PDF 导出功能不可用:缺少 Pango 系统依赖',
|
||||||
|
'details': '请查看 requirements.txt 文件中的 "===== PDF生成 =====" 部分了解如何安装 Pango',
|
||||||
|
'system_message': pango_message
|
||||||
|
}), 503
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
if not data or 'document_ir' not in data:
|
if not data or 'document_ir' not in data:
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"""
|
||||||
|
检测系统依赖工具
|
||||||
|
用于检测 PDF 生成所需的系统依赖
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
def check_pango_available():
|
||||||
|
"""
|
||||||
|
检测 Pango 库是否可用
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (is_available: bool, message: str)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 尝试导入 weasyprint 并初始化 Pango
|
||||||
|
from weasyprint import HTML
|
||||||
|
from weasyprint.text.ffi import ffi, pango
|
||||||
|
|
||||||
|
# 尝试调用 Pango 函数来确认库可用
|
||||||
|
pango.pango_version()
|
||||||
|
|
||||||
|
return True, "✓ Pango 依赖检测通过,PDF 导出功能可用"
|
||||||
|
except OSError as e:
|
||||||
|
# Pango 库未安装或无法加载
|
||||||
|
error_msg = str(e)
|
||||||
|
if 'pango' in error_msg.lower():
|
||||||
|
return False, (
|
||||||
|
"⚠ Pango 依赖未安装或无法加载,PDF 导出功能将不可用(其他功能不受影响)\n"
|
||||||
|
" 请查看 requirements.txt 文件中的 PDF 生成部分,了解如何安装 Pango 依赖"
|
||||||
|
)
|
||||||
|
return False, f"⚠ PDF 依赖加载失败: {error_msg}"
|
||||||
|
except ImportError as e:
|
||||||
|
# weasyprint 未安装
|
||||||
|
return False, f"⚠ WeasyPrint 未安装: {e}"
|
||||||
|
except Exception as e:
|
||||||
|
# 其他未知错误
|
||||||
|
return False, f"⚠ PDF 依赖检测失败: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
def log_dependency_status():
|
||||||
|
"""
|
||||||
|
记录系统依赖状态到日志
|
||||||
|
"""
|
||||||
|
is_available, message = check_pango_available()
|
||||||
|
|
||||||
|
if is_available:
|
||||||
|
logger.success(message)
|
||||||
|
else:
|
||||||
|
logger.warning(message)
|
||||||
|
logger.info("提示:PDF 导出功能需要 Pango 库支持,但不影响系统其他功能的正常使用")
|
||||||
|
logger.info("安装说明请参考:requirements.txt 文件中的 '===== PDF生成 =====' 部分")
|
||||||
|
|
||||||
|
return is_available
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 用于独立测试
|
||||||
|
is_available, message = check_pango_available()
|
||||||
|
print(message)
|
||||||
|
sys.exit(0 if is_available else 1)
|
||||||
Reference in New Issue
Block a user