1+ import os
12import yaml
23import report as r
4+ from enum import StrEnum
5+ from typing import Type
36
4- class MetadataManager () :
7+ class MetadataManager :
58 """
6- Class for handling metadata of reports from YAML files.
9+ Class for handling metadata of reports from YAML files and creating report objects .
710 """
8- def load_report_metadata (self , file_path : str ) -> r .Report :
11+
12+ def load_report_metadata (self , file_path : str ) -> tuple [r .Report , dict ]:
913 """
10- Load and parse the metadata from a YAML file and return a Report object.
14+ Loads metadata from a YAML file, parses it, and returns a Report object and the raw metadata .
1115
1216 Parameters
1317 ----------
1418 file_path : str
1519 The path to the YAML file containing the report metadata.
16-
20+
1721 Returns
1822 -------
19- Report
20- A Report object created from the metadata in the YAML file.
23+ report, metadata : tuple[Report, dict]
24+ A tuple containing the Report object created from the YAML metadata and the raw metadata dictionary.
25+
26+ Raises
27+ ------
28+ FileNotFoundError
29+ If the specified file does not exist.
30+ ValueError
31+ If the YAML file is corrupted or contains missing/invalid values.
2132 """
33+ # Check the existence of the file_path
34+ if not os .path .exists (file_path ):
35+ raise FileNotFoundError (f"The config file at { file_path } was not found." )
36+
37+ # Load the YAML configuration file
2238 with open (file_path , 'r' ) as file :
23- metadata = yaml .safe_load (file )
24-
25- # Create a Report object
39+ try :
40+ metadata = yaml .safe_load (file )
41+ except yaml .YAMLError as exc :
42+ raise ValueError (f"Error parsing YAML file: { exc } " )
43+
44+ # Create a Report object from metadata
2645 report = r .Report (
2746 id = metadata ['report' ]['id' ],
2847 name = metadata ['report' ]['name' ],
@@ -33,83 +52,201 @@ def load_report_metadata(self, file_path: str) -> r.Report:
3352 sections = []
3453 )
3554
36- # Create Sections
37- for section_data in metadata ['sections' ]:
38- section = r .Section (
39- id = section_data ['id' ],
40- name = section_data ['name' ],
41- title = section_data .get ('title' ),
42- description = section_data .get ('description' ),
43- subsections = []
44- )
45-
46- # Create Subsections
47- for subsection_data in section_data ['subsections' ]:
48- subsection = r .Subsection (
49- id = subsection_data ['id' ],
50- name = subsection_data ['name' ],
51- title = subsection_data .get ('title' ),
52- description = subsection_data .get ('description' ),
53- components = []
54- )
55-
56- # Create Components
57- for component_data in subsection_data ['components' ]:
58- component_type = r .ComponentType [component_data ['component_type' ].upper ()]
59- file_path = component_data ['file_path' ]
60- id = component_data ['id' ]
61- name = component_data ['name' ]
62- title = component_data .get ('title' )
63- caption = component_data .get ('caption' )
64-
65- # Define a component based on its type
66- if component_type == r .ComponentType .PLOT :
67- plot_type = r .PlotType [component_data ['plot_type' ].upper ()]
68- int_visualization_tool = (r .IntVisualizationTool [component_data ['int_visualization_tool' ].upper ()]
69- if component_data .get ('int_visualization_tool' ) else None )
70- csv_network_format = (r .CSVNetworkFormat [component_data ['csv_network_format' ].upper ()]
71- if component_data .get ('csv_network_format' ) else None )
72- # Create a Plot component
73- component = r .Plot (
74- id = id ,
75- name = name ,
76- file_path = file_path ,
77- plot_type = plot_type ,
78- int_visualization_tool = int_visualization_tool ,
79- title = title ,
80- caption = caption ,
81- csv_network_format = csv_network_format
82- )
83-
84- elif component_type == r .ComponentType .DATAFRAME :
85- file_format = r .DataFrameFormat [component_data ['file_format' ].upper ()]
86- delimiter = component_data .get ('delimiter' )
87- # Create a DataFrame component
88- component = r .DataFrame (
89- id = id ,
90- name = name ,
91- file_path = file_path ,
92- file_format = file_format ,
93- delimiter = delimiter ,
94- title = title ,
95- caption = caption
96- )
97-
98- elif component_type == r .ComponentType .MARKDOWN :
99- # Create a Markdown component
100- component = r .Markdown (
101- id = id ,
102- name = name ,
103- file_path = file_path ,
104- component_type = component_type ,
105- title = title ,
106- caption = caption
107- )
108-
109- subsection .components .append (component )
110-
111- section .subsections .append (subsection )
112-
55+ # Create sections and subsections
56+ for section_data in metadata .get ('sections' , []):
57+ section = self ._create_section (section_data )
11358 report .sections .append (section )
11459
115- return report , metadata
60+ return report , metadata
61+
62+ def _create_section (self , section_data : dict ) -> r .Section :
63+ """
64+ Creates a Section object from a dictionary of section data.
65+
66+ Parameters
67+ ----------
68+ section_data : dict
69+ A dictionary containing section metadata.
70+
71+ Returns
72+ -------
73+ section : Section
74+ A Section object populated with the provided metadata.
75+ """
76+ # Initialize the Section object
77+ section = r .Section (
78+ id = section_data ['id' ],
79+ name = section_data ['name' ],
80+ title = section_data .get ('title' ),
81+ description = section_data .get ('description' ),
82+ subsections = []
83+ )
84+
85+ # Create subsections
86+ for subsection_data in section_data .get ('subsections' , []):
87+ subsection = self ._create_subsection (subsection_data )
88+ section .subsections .append (subsection )
89+
90+ return section
91+
92+ def _create_subsection (self , subsection_data : dict ) -> r .Subsection :
93+ """
94+ Creates a Subsection object from a dictionary of subsection data.
95+
96+ Parameters
97+ ----------
98+ subsection_data : dict
99+ A dictionary containing subsection metadata.
100+
101+ Returns
102+ -------
103+ subsection : Subsection
104+ A Subsection object populated with the provided metadata.
105+ """
106+ # Initialize the Subsection object
107+ subsection = r .Subsection (
108+ id = subsection_data ['id' ],
109+ name = subsection_data ['name' ],
110+ title = subsection_data .get ('title' ),
111+ description = subsection_data .get ('description' ),
112+ components = []
113+ )
114+
115+ # Create components
116+ for component_data in subsection_data .get ('components' , []):
117+ component = self ._create_component (component_data )
118+ subsection .components .append (component )
119+
120+ return subsection
121+
122+ def _create_component (self , component_data : dict ) -> r .Component :
123+ """
124+ Creates a Component object from a dictionary of component data.
125+
126+ Parameters
127+ ----------
128+ component_data : dict
129+ A dictionary containing component metadata.
130+
131+ Returns
132+ -------
133+ Component
134+ A Component object (Plot, DataFrame, or Markdown) populated with the provided metadata.
135+ """
136+ # Determine the component type
137+ component_type = self ._validate_enum_value (r .ComponentType , component_data ['component_type' ])
138+
139+ # Dispatch to the corresponding creation method
140+ if component_type == r .ComponentType .PLOT :
141+ return self ._create_plot_component (component_data )
142+ elif component_type == r .ComponentType .DATAFRAME :
143+ return self ._create_dataframe_component (component_data )
144+ elif component_type == r .ComponentType .MARKDOWN :
145+ return self ._create_markdown_component (component_data )
146+
147+ def _create_plot_component (self , component_data : dict ) -> r .Plot :
148+ """
149+ Creates a Plot component.
150+
151+ Parameters
152+ ----------
153+ component_data : dict
154+ A dictionary containing plot component metadata.
155+
156+ Returns
157+ -------
158+ Plot
159+ A Plot object populated with the provided metadata.
160+ """
161+ # Validate enum fields
162+ plot_type = self ._validate_enum_value (r .PlotType , component_data ['plot_type' ])
163+ int_visualization_tool = (self ._validate_enum_value (r .IntVisualizationTool , component_data .get ('int_visualization_tool' , '' ))
164+ if component_data .get ('int_visualization_tool' ) else None )
165+ csv_network_format = (self ._validate_enum_value (r .CSVNetworkFormat , component_data .get ('csv_network_format' , '' ))
166+ if component_data .get ('csv_network_format' ) else None )
167+
168+ # Return the constructed Plot object
169+ return r .Plot (
170+ id = component_data ['id' ],
171+ name = component_data ['name' ],
172+ file_path = component_data ['file_path' ],
173+ plot_type = plot_type ,
174+ int_visualization_tool = int_visualization_tool ,
175+ title = component_data .get ('title' ),
176+ caption = component_data .get ('caption' ),
177+ csv_network_format = csv_network_format
178+ )
179+
180+ def _create_dataframe_component (self , component_data : dict ) -> r .DataFrame :
181+ """
182+ Creates a DataFrame component.
183+
184+ Parameters
185+ ----------
186+ component_data : dict
187+ A dictionary containing dataframe component metadata.
188+
189+ Returns
190+ -------
191+ DataFrame
192+ A DataFrame object populated with the provided metadata.
193+ """
194+ # Validate enum field and return dataframe
195+ file_format = self ._validate_enum_value (r .DataFrameFormat , component_data ['file_format' ])
196+ return r .DataFrame (
197+ id = component_data ['id' ],
198+ name = component_data ['name' ],
199+ file_path = component_data ['file_path' ],
200+ file_format = file_format ,
201+ delimiter = component_data .get ('delimiter' ),
202+ title = component_data .get ('title' ),
203+ caption = component_data .get ('caption' )
204+ )
205+
206+ def _create_markdown_component (self , component_data : dict ) -> r .Markdown :
207+ """
208+ Creates a Markdown component.
209+
210+ Parameters
211+ ----------
212+ component_data : dict
213+ A dictionary containing markdown component metadata.
214+
215+ Returns
216+ -------
217+ Markdown
218+ A Markdown object populated with the provided metadata.
219+ """
220+ return r .Markdown (
221+ id = component_data ['id' ],
222+ name = component_data ['name' ],
223+ file_path = component_data ['file_path' ],
224+ title = component_data .get ('title' ),
225+ caption = component_data .get ('caption' )
226+ )
227+
228+ def _validate_enum_value (self , enum_class : Type [StrEnum ], value : str ) -> StrEnum :
229+ """
230+ Validate that the given value is a valid member of the specified enumeration class.
231+
232+ Parameters
233+ ----------
234+ enum_class : Type[StrEnum]
235+ The enumeration class to validate against.
236+ value : str
237+ The value to be validated.
238+
239+ Returns
240+ -------
241+ StrEnum
242+ The corresponding member of the enumeration if valid.
243+
244+ Raises
245+ ------
246+ ValueError
247+ If the value is not a valid member of the enumeration class.
248+ """
249+ try :
250+ return enum_class [value .upper ()]
251+ except KeyError :
252+ raise ValueError (f"Invalid { enum_class .__name__ } : { value } " )
0 commit comments