SIENTIAPDE-1255: Add thread-safety documentation and I/O notes to Reports class, improve save_all_sections documentation and error handling.
This commit is contained in:
@@ -55,6 +55,22 @@ def inject_content(main_html, section_id, content):
|
|||||||
|
|
||||||
|
|
||||||
class Reports:
|
class Reports:
|
||||||
|
"""
|
||||||
|
Report generator using Evidently library.
|
||||||
|
|
||||||
|
Thread-safety: This class is NOT thread-safe. Multiple threads should not
|
||||||
|
call add_*_section() methods on the same instance simultaneously as they
|
||||||
|
modify shared state (self.metrics, self.sections, self.options).
|
||||||
|
|
||||||
|
For multi-threaded environments:
|
||||||
|
- Create separate Reports instances per thread
|
||||||
|
- Or synchronize access using locks
|
||||||
|
- After generation, instances are safe for read-only operations
|
||||||
|
|
||||||
|
I/O Note: This class relies on Evidently's report.save_html() method
|
||||||
|
for file operations. Ensure Evidently properly manages file handles.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, reference_data: Any, current_data: Any, base_path: str | None = None
|
self, reference_data: Any, current_data: Any, base_path: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -95,6 +111,7 @@ class Reports:
|
|||||||
report.run(reference_data=self.ref_data, current_data=self.cur_data)
|
report.run(reference_data=self.ref_data, current_data=self.cur_data)
|
||||||
self.sections['data_quality'] = report.as_dict()
|
self.sections['data_quality'] = report.as_dict()
|
||||||
if self.base_path:
|
if self.base_path:
|
||||||
|
# Note: Relies on Evidently's save_html() to properly manage file I/O
|
||||||
report.save_html(os.path.join(self.base_path, 'data_quality.html'))
|
report.save_html(os.path.join(self.base_path, 'data_quality.html'))
|
||||||
|
|
||||||
def add_data_drift_section(self, columns: list[str] | None = None, run: bool = True) -> None:
|
def add_data_drift_section(self, columns: list[str] | None = None, run: bool = True) -> None:
|
||||||
@@ -111,6 +128,7 @@ class Reports:
|
|||||||
report.run(reference_data=self.ref_data, current_data=self.cur_data)
|
report.run(reference_data=self.ref_data, current_data=self.cur_data)
|
||||||
self.sections['data_drift'] = report.as_dict()
|
self.sections['data_drift'] = report.as_dict()
|
||||||
if self.base_path:
|
if self.base_path:
|
||||||
|
# Note: Relies on Evidently's save_html() to properly manage file I/O
|
||||||
report.save_html(os.path.join(self.base_path, 'data_drift.html'))
|
report.save_html(os.path.join(self.base_path, 'data_drift.html'))
|
||||||
|
|
||||||
def add_regression_section(self, run: bool = True) -> None:
|
def add_regression_section(self, run: bool = True) -> None:
|
||||||
@@ -135,6 +153,7 @@ class Reports:
|
|||||||
report.run(reference_data=self.ref_data, current_data=self.cur_data)
|
report.run(reference_data=self.ref_data, current_data=self.cur_data)
|
||||||
self.sections['regression'] = report.as_dict()
|
self.sections['regression'] = report.as_dict()
|
||||||
if self.base_path:
|
if self.base_path:
|
||||||
|
# Note: Relies on Evidently's save_html() to properly manage file I/O
|
||||||
report.save_html(os.path.join(self.base_path, 'regression.html'))
|
report.save_html(os.path.join(self.base_path, 'regression.html'))
|
||||||
|
|
||||||
def set_color_options(
|
def set_color_options(
|
||||||
@@ -201,10 +220,23 @@ class Reports:
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
report_path: The path to save the report HTML file.
|
report_path: The path to save the report HTML file.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If base_path is not set
|
||||||
|
OSError: If directory creation or file writing fails
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method uses context manager (with open) to ensure file is properly closed.
|
||||||
|
Creates parent directories if they don't exist.
|
||||||
"""
|
"""
|
||||||
if not self.base_path:
|
if not self.base_path:
|
||||||
raise ValueError('base_path is required to save all sections HTML')
|
raise ValueError('base_path is required to save all sections HTML')
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
output_dir = os.path.dirname(report_path)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
# Load main HTML template
|
# Load main HTML template
|
||||||
main_html_path = os.path.join(self.base_path, 'header.html')
|
main_html_path = os.path.join(self.base_path, 'header.html')
|
||||||
main_html = load_html_from_file(main_html_path)
|
main_html = load_html_from_file(main_html_path)
|
||||||
@@ -222,5 +254,6 @@ class Reports:
|
|||||||
main_html = inject_content(main_html, 'regression', regression_content)
|
main_html = inject_content(main_html, 'regression', regression_content)
|
||||||
|
|
||||||
# Save the final HTML to a new file (report.html)
|
# Save the final HTML to a new file (report.html)
|
||||||
|
# Context manager ensures file is properly closed even if an error occurs
|
||||||
with open(report_path, 'w', encoding='utf-8') as report_file:
|
with open(report_path, 'w', encoding='utf-8') as report_file:
|
||||||
report_file.write(main_html)
|
report_file.write(main_html)
|
||||||
|
|||||||
Reference in New Issue
Block a user