@@ -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()
380418display.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