Skip to content

Commit 94ad77f

Browse files
committed
Refactor MetadataManager class to separate load_report_metadata into smaller methods. Also, add error handling and better docstring descriptions.
1 parent 295a095 commit 94ad77f

File tree

5 files changed

+239
-98
lines changed

5 files changed

+239
-98
lines changed

report/main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
doc_report.generate_report(output_dir="quarto_report/")
1919
doc_report.run_report(output_dir="quarto_report/")
2020

21-
# st_report = st_reportview.StreamlitReportView(report_metadata['report']['id'], report_metadata['report']['name'],
22-
# report=report, report_type = ReportType[report_metadata['report']['report_type'].upper()], columns=None)
23-
# st_report.generate_report(output_dir="streamlit_report/sections")
24-
# st_report.run_report(output_dir="streamlit_report/sections")
21+
st_report = st_reportview.StreamlitReportView(report_metadata['report']['id'], report_metadata['report']['name'],
22+
report=report, report_type = ReportType[report_metadata['report']['report_type'].upper()], columns=None)
23+
st_report.generate_report(output_dir="streamlit_report/sections")
24+
st_report.run_report(output_dir="streamlit_report/sections")
2525

report/metadata_manager.py

Lines changed: 225 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
1+
import os
12
import yaml
23
import 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}")

report/quarto_reportview.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def _create_yaml_header(self) -> str:
169169

170170
return yaml_header
171171

172-
def _generate_subsection(self, subsection, is_report_static, is_report_revealjs) -> List[str]:
172+
def _generate_subsection(self, subsection, is_report_static, is_report_revealjs) -> tuple[List[str], List[str]]:
173173
"""
174174
Generate code to render components (plots, dataframes, markdown) in the given subsection,
175175
creating imports and content for the subsection based on the component type.

report/report.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import os
22
from abc import ABC, abstractmethod
33
from dataclasses import dataclass, field
4-
from enum import StrEnum, Enum, auto
5-
from typing import List, Optional, NamedTuple
4+
from enum import StrEnum, auto
5+
from typing import List, Optional
66
import networkx as nx
77
import pandas as pd
88
import matplotlib.pyplot as plt
@@ -315,7 +315,11 @@ class Markdown(Component):
315315
"""
316316
A Markdown text component within a subsection of a report.
317317
"""
318-
component_type = ComponentType.MARKDOWN
318+
def __init__(self, id: int, name: str, file_path: str, title: str=None, caption: str=None):
319+
"""
320+
Initializes a DataFrame object.
321+
"""
322+
super().__init__(id, name, file_path, component_type=ComponentType.MARKDOWN, title=title, caption=caption)
319323

320324
@dataclass
321325
class Subsection:
@@ -507,7 +511,7 @@ def _generate_sections(self, output_dir: str) -> None:
507511
pass
508512

509513
@abstractmethod
510-
def _generate_subsection(self, subsection: Subsection) -> List[str]:
514+
def _generate_subsection(self, subsection: Subsection) -> tuple[List[str], List[str]]:
511515
"""
512516
Generate code to render components (plots, dataframes, markdown) in the given subsection,
513517
creating imports and content for the subsection based on the component type.

0 commit comments

Comments
 (0)