Skip to content

Commit 41e1e07

Browse files
committed
Add error handling code and logging into the StreamlitReportView class
1 parent 47d9ef6 commit 41e1e07

File tree

3 files changed

+163
-124
lines changed

3 files changed

+163
-124
lines changed

report/quarto_reportview.py

Lines changed: 140 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -35,59 +35,69 @@ def generate_report(self, output_dir: str = BASE_DIR) -> None:
3535
output_dir : str, optional
3636
The folder where the generated report files will be saved (default is BASE_DIR).
3737
"""
38+
self.report.logger.debug(f"Generating '{self.report_type}' report with '{self.report_format}' format in directory: '{output_dir}'")
39+
3840
# Create the output folder if it does not exist
3941
if not os.path.exists(output_dir):
4042
os.mkdir(output_dir)
41-
42-
# Create variable to check if the report is static or revealjs
43-
is_report_static = self.report_format in {ReportFormat.PDF, ReportFormat.DOCX, ReportFormat.ODT, ReportFormat.PPTX}
44-
is_report_revealjs = self.report_format == ReportFormat.REVEALJS
45-
46-
# Define the YAML header for the quarto report
47-
yaml_header = self._create_yaml_header()
48-
49-
# Create qmd content and imports for the report
50-
qmd_content = []
51-
report_imports = []
52-
53-
# Add the title and description of the report
54-
qmd_content.append(f'''{self.report.description}\n''')
55-
56-
# If available add the graphical abstract
57-
if self.report.graphical_abstract:
58-
qmd_content.append(self._generate_image_content(self.report.graphical_abstract, f"Graphical abstract for the {self.report.title} report"))
59-
# Add the sections and subsections to the report
60-
for section in self.report.sections:
61-
# Add section header and description
62-
qmd_content.append(f'# {section.title}')
63-
qmd_content.append(f'''{section.description}''')
43+
self.report.logger.debug(f"Created output directory: {output_dir}")
44+
try:
45+
# Create variable to check if the report is static or revealjs
46+
is_report_static = self.report_format in {ReportFormat.PDF, ReportFormat.DOCX, ReportFormat.ODT, ReportFormat.PPTX}
47+
is_report_revealjs = self.report_format == ReportFormat.REVEALJS
6448

65-
if section.subsections:
66-
# Iterate through subsections and integrate them into the section file
67-
for subsection in section.subsections:
68-
# Generate content for the subsection
69-
subsection_content, subsection_imports = self._generate_subsection(subsection, is_report_static, is_report_revealjs)
70-
qmd_content.extend(subsection_content)
71-
report_imports.extend(subsection_imports)
72-
73-
# Flatten the subsection_imports into a single list
74-
flattened_report_imports = [imp for sublist in report_imports for imp in sublist]
75-
76-
# Remove duplicated imports
77-
report_unique_imports = list(set(flattened_report_imports))
49+
# Define the YAML header for the quarto report
50+
yaml_header = self._create_yaml_header()
51+
52+
# Create qmd content and imports for the report
53+
qmd_content = []
54+
report_imports = []
55+
56+
# Add the title and description of the report
57+
qmd_content.append(f'''{self.report.description}\n''')
58+
59+
# If available add the graphical abstract
60+
if self.report.graphical_abstract:
61+
qmd_content.append(self._generate_image_content(self.report.graphical_abstract, f"Graphical abstract for the {self.report.title} report"))
62+
# Add the sections and subsections to the report
63+
self.report.logger.info("Starting to generate sections for the report.")
64+
for section in self.report.sections:
65+
self.report.logger.debug(f"Processing section: '{section.name}' - {len(section.subsections)} subsection(s)")
66+
# Add section header and description
67+
qmd_content.append(f'# {section.title}')
68+
qmd_content.append(f'''{section.description}''')
69+
70+
if section.subsections:
71+
# Iterate through subsections and integrate them into the section file
72+
for subsection in section.subsections:
73+
self.report.logger.debug(f"Processing subsection: '{subsection.name}' - {len(subsection.components)} component(s)")
74+
# Generate content for the subsection
75+
subsection_content, subsection_imports = self._generate_subsection(subsection, is_report_static, is_report_revealjs)
76+
qmd_content.extend(subsection_content)
77+
report_imports.extend(subsection_imports)
78+
79+
# Flatten the subsection_imports into a single list
80+
flattened_report_imports = [imp for sublist in report_imports for imp in sublist]
81+
82+
# Remove duplicated imports
83+
report_unique_imports = list(set(flattened_report_imports))
7884

79-
# Format imports
80-
report_formatted_imports = "\n".join(report_unique_imports)
81-
82-
# Write the navigation and general content to a Python file
83-
with open(os.path.join(output_dir, "quarto_report.qmd"), 'w') as quarto_report:
84-
quarto_report.write(yaml_header)
85-
quarto_report.write(f"""\n```{{python}}
85+
# Format imports
86+
report_formatted_imports = "\n".join(report_unique_imports)
87+
88+
# Write the navigation and general content to a Python file
89+
with open(os.path.join(output_dir, f"{self.BASE_DIR}.qmd"), 'w') as quarto_report:
90+
quarto_report.write(yaml_header)
91+
quarto_report.write(f"""\n```{{python}}
8692
#| label: 'Imports'
8793
#| echo: false
8894
{report_formatted_imports}
8995
```\n\n""")
90-
quarto_report.write("\n".join(qmd_content))
96+
quarto_report.write("\n".join(qmd_content))
97+
self.report.logger.info(f"Created qmd script to render the app: {self.BASE_DIR}.qmd")
98+
except Exception as e:
99+
self.report.logger.error(f"An error occurred while generating the report: {str(e)}")
100+
raise
91101

92102
def run_report(self, output_dir: str = BASE_DIR) -> None:
93103
"""
@@ -98,7 +108,12 @@ def run_report(self, output_dir: str = BASE_DIR) -> None:
98108
output_dir : str, optional
99109
The folder where the report was generated (default is 'sections').
100110
"""
101-
subprocess.run(["quarto", "render", os.path.join(output_dir, "quarto_report.qmd")], check=True)
111+
try:
112+
subprocess.run(["quarto", "render", os.path.join(output_dir, f"{self.BASE_DIR}.qmd")], check=True)
113+
self.report.logger.info(f"'{self.name}' {self.report_type} report rendered with the {self.report_format} format")
114+
except subprocess.CalledProcessError as e:
115+
self.report.logger.error(f"Error running '{self.name}' {self.report_type} report: {str(e)}")
116+
raise
102117

103118
def _create_yaml_header(self) -> str:
104119
"""
@@ -206,10 +221,13 @@ def _generate_subsection(self, subsection, is_report_static, is_report_revealjs)
206221

207222
elif component.component_type == r.ComponentType.MARKDOWN:
208223
subsection_content.extend(self._generate_markdown_content(component))
224+
else:
225+
self.report.logger.warning(f"Unsupported component type '{component.component_type}' in subsection: {subsection.name}")
209226

210227
if is_report_revealjs:
211228
subsection_content.append(':::\n')
212-
229+
230+
self.report.logger.info(f"Generated content and imports for subsection: '{subsection.name}'")
213231
return subsection_content, subsection_imports
214232

215233
def _generate_plot_content(self, plot, is_report_static, output_dir: str = STATIC_FILES_DIR) -> List[str]:
@@ -237,42 +255,54 @@ def _generate_plot_content(self, plot, is_report_static, output_dir: str = STATI
237255
plot_content = []
238256
plot_content.append(f'### {plot.title}')
239257
if plot.plot_type == r.PlotType.INTERACTIVE:
240-
# Define plot path
241-
if is_report_static:
242-
static_plot_path = os.path.join(output_dir, f"{plot.name.replace(' ', '_')}.png")
243-
else:
244-
html_plot_file = os.path.join(output_dir, f"{plot.name.replace(' ', '_')}.html")
245-
246-
if plot.int_visualization_tool == r.IntVisualizationTool.PLOTLY:
247-
plot_content.append(self._generate_plot_code(plot))
248-
if is_report_static:
249-
plot_content.append(f"""fig_plotly.write_image("{os.path.join("..", static_plot_path)}")\n```\n""")
250-
plot_content.append(self._generate_image_content(static_plot_path, plot.name))
251-
else:
252-
plot_content.append(f"""fig_plotly.show()\n```\n""")
253-
elif plot.int_visualization_tool == r.IntVisualizationTool.ALTAIR:
254-
plot_content.append(self._generate_plot_code(plot))
258+
try:
259+
# Define plot path
255260
if is_report_static:
256-
plot_content.append(f"""fig_altair.save("{os.path.join("..", static_plot_path)}")\n```\n""")
257-
plot_content.append(self._generate_image_content(static_plot_path, plot.name))
261+
static_plot_path = os.path.join(output_dir, f"{plot.name.replace(' ', '_')}.png")
258262
else:
259-
plot_content.append(f"""fig_altair\n```\n""")
260-
elif plot.int_visualization_tool == r.IntVisualizationTool.PYVIS:
261-
G = plot.read_network()
262-
num_nodes = G.number_of_nodes()
263-
num_edges = G.number_of_edges()
264-
plot_content.append(f'**Number of nodes:** {num_nodes}\n')
265-
plot_content.append(f'**Number of edges:** {num_edges}\n')
266-
if is_report_static:
267-
plot.save_netwrok_image(G, static_plot_path, "png")
268-
plot_content.append(self._generate_image_content(static_plot_path, plot.name))
263+
html_plot_file = os.path.join(output_dir, f"{plot.name.replace(' ', '_')}.html")
264+
265+
if plot.int_visualization_tool == r.IntVisualizationTool.PLOTLY:
266+
plot_content.append(self._generate_plot_code(plot))
267+
if is_report_static:
268+
plot_content.append(f"""fig_plotly.write_image("{os.path.join("..", static_plot_path)}")\n```\n""")
269+
plot_content.append(self._generate_image_content(static_plot_path, plot.name))
270+
else:
271+
plot_content.append(f"""fig_plotly.show()\n```\n""")
272+
elif plot.int_visualization_tool == r.IntVisualizationTool.ALTAIR:
273+
plot_content.append(self._generate_plot_code(plot))
274+
if is_report_static:
275+
plot_content.append(f"""fig_altair.save("{os.path.join("..", static_plot_path)}")\n```\n""")
276+
plot_content.append(self._generate_image_content(static_plot_path, plot.name))
277+
else:
278+
plot_content.append(f"""fig_altair\n```\n""")
279+
elif plot.int_visualization_tool == r.IntVisualizationTool.PYVIS:
280+
G = plot.read_network()
281+
num_nodes = G.number_of_nodes()
282+
num_edges = G.number_of_edges()
283+
plot_content.append(f'**Number of nodes:** {num_nodes}\n')
284+
plot_content.append(f'**Number of edges:** {num_edges}\n')
285+
if is_report_static:
286+
plot.save_netwrok_image(G, static_plot_path, "png")
287+
plot_content.append(self._generate_image_content(static_plot_path, plot.name))
288+
else:
289+
# Get the Network object
290+
net = plot.create_and_save_pyvis_network(G, html_plot_file)
291+
plot_content.append(self._generate_plot_code(plot, html_plot_file))
269292
else:
270-
# Get the Network object
271-
net = plot.create_and_save_pyvis_network(G, html_plot_file)
272-
plot_content.append(self._generate_plot_code(plot, html_plot_file))
293+
self.report.logger.warning(f"Unsupported interactive plot tool: {plot.int_visualization_tool}")
294+
except Exception as e:
295+
self.report.logger.error(f"Error generating interactive plot content for {plot.name}: {str(e)}")
296+
raise
297+
273298
elif plot.plot_type == r.PlotType.STATIC:
274-
plot_content.append(self._generate_image_content(plot.file_path, width=950))
299+
try:
300+
plot_content.append(self._generate_image_content(plot.file_path, width=950))
301+
except Exception as e:
302+
self.report.logger.error(f"Error generating static plot content for {plot.name}: {str(e)}")
303+
raise
275304

305+
self.report.logger.info(f"Successfully generated content for plot: '{plot.name}'")
276306
return plot_content
277307

278308
def _generate_plot_code(self, plot, output_file = "") -> str:
@@ -333,25 +363,32 @@ def _generate_dataframe_content(self, dataframe, is_report_static) -> List[str]:
333363
#| label: {dataframe.name}
334364
#| echo: false
335365
""")
336-
if dataframe.file_format == r.DataFrameFormat.CSV:
337-
if dataframe.delimiter:
338-
datframe_content.append(f"""df = pd.read_csv('{os.path.join("..", dataframe.file_path)}', delimiter='{dataframe.delimiter}')""")
366+
try:
367+
if dataframe.file_format == r.DataFrameFormat.CSV:
368+
if dataframe.delimiter:
369+
datframe_content.append(f"""df = pd.read_csv('{os.path.join("..", dataframe.file_path)}', delimiter='{dataframe.delimiter}')""")
370+
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
371+
else:
372+
datframe_content.append(f"""df = pd.read_csv('{os.path.join("..", dataframe.file_path)}')""")
373+
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
374+
elif dataframe.file_format == r.DataFrameFormat.PARQUET:
375+
datframe_content.append(f"""df = pd.read_parquet('{os.path.join("..", dataframe.file_path)}')""")
339376
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
340-
else:
341-
datframe_content.append(f"""df = pd.read_csv('{os.path.join("..", dataframe.file_path)}')""")
377+
elif dataframe.file_format == r.DataFrameFormat.TXT:
378+
datframe_content.append(f"""df = pd.read_csv('{os.path.join("..", dataframe.file_path)}', sep='\\t')""")
342379
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
343-
elif dataframe.file_format == r.DataFrameFormat.PARQUET:
344-
datframe_content.append(f"""df = pd.read_parquet('{os.path.join("..", dataframe.file_path)}')""")
345-
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
346-
elif dataframe.file_format == r.DataFrameFormat.TXT:
347-
datframe_content.append(f"""df = pd.read_csv('{os.path.join("..", dataframe.file_path)}', sep='\\t')""")
348-
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
349-
elif dataframe.file_format == r.DataFrameFormat.EXCEL:
350-
datframe_content.append(f"""df = pd.read_excel('{os.path.join("..", dataframe.file_path)}')""")
351-
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
352-
else:
353-
raise ValueError(f"Unsupported DataFrame file format: {dataframe.file_format}")
354-
380+
elif dataframe.file_format == r.DataFrameFormat.EXCEL:
381+
datframe_content.append(f"""df = pd.read_excel('{os.path.join("..", dataframe.file_path)}')""")
382+
datframe_content.extend(self._show_dataframe(dataframe, is_report_static))
383+
else:
384+
self.report.logger.error(f"Unsupported DataFrame file format: {dataframe.file_format}")
385+
raise ValueError(f"Unsupported DataFrame file format: {dataframe.file_format}")
386+
387+
except Exception as e:
388+
self.report.logger.error(f"Error generating content for DataFrame: {dataframe.title}. Error: {str(e)}")
389+
raise
390+
391+
self.report.logger.info(f"Successfully generated content for DataFrame: '{dataframe.title}'")
355392
return datframe_content
356393

357394
def _generate_markdown_content(self, markdown) -> List[str]:
@@ -368,9 +405,10 @@ def _generate_markdown_content(self, markdown) -> List[str]:
368405
list : List[str]
369406
The list of content lines for the markdown.
370407
"""
371-
markdown_content = []
372-
markdown_content.append(f'### {markdown.title}')
373-
markdown_content.append(f"""```{{python}}
408+
try:
409+
markdown_content = []
410+
markdown_content.append(f'### {markdown.title}')
411+
markdown_content.append(f"""```{{python}}
374412
#| label: {markdown.name}
375413
#| table-cap: "MD file"
376414
#| table-type: "md"
@@ -379,6 +417,11 @@ def _generate_markdown_content(self, markdown) -> List[str]:
379417
markdown_content = markdown_file.read()
380418
display.Markdown(markdown_content)
381419
```\n""")
420+
except Exception as e:
421+
self.report.logger.error(f"Error generating content for Markdown: {markdown.title}. Error: {str(e)}")
422+
raise
423+
424+
self.report.logger.info(f"Successfully generated content for Markdown: '{markdown.title}'")
382425
return markdown_content
383426

384427
def _generate_image_content(self, image_path: str, alt_text: str = "", width: int = 650, height: int = 400) -> str:

0 commit comments

Comments
 (0)