1313from .utils .variables import make_valid_identifier
1414
1515
16+ def write_python_file (fpath : str , imports : list [str ], contents : list [str ]) -> None :
17+ with open (fpath , "w" , encoding = "utf8" ) as f :
18+ # Write imports at the top of the file
19+ f .write ("\n " .join (imports ) + "\n \n " )
20+
21+ # Write the subsection content (descriptions, plots)
22+ f .write ("\n " .join (contents ))
23+
24+
1625class StreamlitReportView (r .WebAppReportView ):
1726 """
1827 A Streamlit-based implementation of the WebAppReportView abstract base class.
@@ -38,6 +47,15 @@ def __init__(
3847 else :
3948 self .report .logger .info ("running in a normal Python process" )
4049
50+ self .components_fct_map = {
51+ r .ComponentType .PLOT : self ._generate_plot_content ,
52+ r .ComponentType .DATAFRAME : self ._generate_dataframe_content ,
53+ r .ComponentType .MARKDOWN : self ._generate_markdown_content ,
54+ r .ComponentType .HTML : self ._generate_html_content ,
55+ r .ComponentType .APICALL : self ._generate_apicall_content ,
56+ r .ComponentType .CHATBOT : self ._generate_chatbot_content ,
57+ }
58+
4159 def generate_report (
4260 self , output_dir : str = SECTIONS_DIR , static_dir : str = STATIC_FILES_DIR
4361 ) -> None :
@@ -338,65 +356,79 @@ def _generate_sections(self, output_dir: str, static_dir: str) -> None:
338356 f"Processing section '{ section .id } ': '{ section .title } ' - { len (section .subsections )} subsection(s)"
339357 )
340358
341- if section .subsections :
342- # Iterate through subsections and integrate them into the section file
343- for subsection in section .subsections :
344- self .report .logger .debug (
345- f"Processing subsection '{ subsection .id } ': '{ subsection .title } - { len (subsection .components )} component(s)'"
346- )
347- try :
348- # Create subsection file
349- _subsection_name = make_valid_identifier (subsection .title )
350- subsection_file_path = (
351- Path (output_dir )
352- / section_name_var
353- / f"{ _subsection_name } .py"
354- )
355-
356- # Generate content and imports for the subsection
357- subsection_content , subsection_imports = (
358- self ._generate_subsection (
359- subsection , static_dir = static_dir
360- )
361- )
362-
363- # Flatten the subsection_imports into a single list
364- flattened_subsection_imports = [
365- imp for sublist in subsection_imports for imp in sublist
366- ]
367-
368- # Remove duplicated imports
369- unique_imports = list (set (flattened_subsection_imports ))
370-
371- # Write everything to the subsection file
372- with open (subsection_file_path , "w" ) as subsection_file :
373- # Write imports at the top of the file
374- subsection_file .write (
375- "\n " .join (unique_imports ) + "\n \n "
376- )
377-
378- # Write the subsection content (descriptions, plots)
379- subsection_file .write ("\n " .join (subsection_content ))
380-
381- self .report .logger .info (
382- f"Subsection file created: '{ subsection_file_path } '"
383- )
384- except Exception as subsection_error :
385- self .report .logger .error (
386- f"Error processing subsection '{ subsection .id } ' '{ subsection .title } ' in section '{ section .id } ' '{ section .title } ': { str (subsection_error )} "
387- )
388- raise
389- else :
359+ if not section .subsections :
390360 self .report .logger .warning (
391- f"No subsections found in section: '{ section .title } '. To show content in the report, add subsections to the section."
361+ f"No subsections found in section: '{ section .title } '. "
362+ "To show content in the report, add subsections to the section."
363+ )
364+ continue
365+
366+ # Iterate through subsections and integrate them into the section file
367+ for subsection in section .subsections :
368+ self .report .logger .debug (
369+ f"Processing subsection '{ subsection .id } ': '{ subsection .title } - { len (subsection .components )} component(s)'"
392370 )
371+ try :
372+ # Create subsection file
373+ _subsection_name = make_valid_identifier (subsection .title )
374+ subsection_file_path = (
375+ Path (output_dir )
376+ / section_name_var
377+ / f"{ _subsection_name } .py"
378+ )
379+
380+ # Generate content and imports for the subsection
381+ subsection_content , subsection_imports = (
382+ self ._generate_subsection (subsection )
383+ )
384+
385+ write_python_file (
386+ fpath = subsection_file_path ,
387+ imports = subsection_imports ,
388+ contents = subsection_content ,
389+ )
390+ self .report .logger .info (
391+ f"Subsection file created: '{ subsection_file_path } '"
392+ )
393+ except Exception as subsection_error :
394+ self .report .logger .error (
395+ f"Error processing subsection '{ subsection .id } ' '{ subsection .title } ' "
396+ f"in section '{ section .id } ' '{ section .title } ': { str (subsection_error )} "
397+ )
398+ raise
399+
393400 except Exception as e :
394401 self .report .logger .error (f"Error generating sections: { str (e )} " )
395402 raise
396403
397- def _generate_subsection (
398- self , subsection , static_dir
399- ) -> tuple [List [str ], List [str ]]:
404+ def _combine_components (self , components : list [dict ]) -> tuple [list , list , bool ]:
405+ """combine a list of components."""
406+
407+ all_contents = []
408+ all_imports = []
409+ has_chatbot = False
410+
411+ for component in components :
412+ # Write imports if not already done
413+ component_imports = self ._generate_component_imports (component )
414+ all_imports .extend (component_imports )
415+
416+ # Handle different types of components
417+ fct = self .components_fct_map .get (component .component_type , None )
418+ if fct is None :
419+ self .report .logger .warning (
420+ f"Unsupported component type '{ component .component_type } ' "
421+ )
422+ else :
423+ if component .component_type == r .ComponentType .CHATBOT :
424+ has_chatbot = True
425+ content = fct (component )
426+ all_contents .extend (content )
427+ # remove duplicates and isort
428+ all_imports = list (set (all_imports ))
429+ return all_contents , all_imports , has_chatbot
430+
431+ def _generate_subsection (self , subsection ) -> tuple [List [str ], List [str ]]:
400432 """
401433 Generate code to render components (plots, dataframes, markdown) in the given subsection,
402434 creating imports and content for the subsection based on the component type.
@@ -430,36 +462,10 @@ def _generate_subsection(
430462 subsection_content .append (
431463 self ._format_text (text = subsection .description , type = "paragraph" )
432464 )
433-
434- for component in subsection .components :
435- # Write imports if not already done
436- component_imports = self ._generate_component_imports (component )
437- subsection_imports .append (component_imports )
438-
439- # Handle different types of components
440- if component .component_type == r .ComponentType .PLOT :
441- subsection_content .extend (
442- self ._generate_plot_content (component , static_dir = static_dir )
443- )
444- elif component .component_type == r .ComponentType .DATAFRAME :
445- subsection_content .extend (self ._generate_dataframe_content (component ))
446- # If md files is called "description.md", do not include it in the report
447- elif (
448- component .component_type == r .ComponentType .MARKDOWN
449- and component .title .lower () != "description"
450- ):
451- subsection_content .extend (self ._generate_markdown_content (component ))
452- elif component .component_type == r .ComponentType .HTML :
453- subsection_content .extend (self ._generate_html_content (component ))
454- elif component .component_type == r .ComponentType .APICALL :
455- subsection_content .extend (self ._generate_apicall_content (component ))
456- elif component .component_type == r .ComponentType .CHATBOT :
457- has_chatbot = True
458- subsection_content .extend (self ._generate_chatbot_content (component ))
459- else :
460- self .report .logger .warning (
461- f"Unsupported component type '{ component .component_type } ' in subsection: { subsection .title } "
462- )
465+ all_components , subsection_imports , has_chatbot = self ._combine_components (
466+ subsection .components
467+ )
468+ subsection_content .extend (all_components )
463469
464470 if not has_chatbot :
465471 # Define the footer variable and add it to the home page content
0 commit comments