1
1
import os
2
2
import subprocess
3
3
import sys
4
+ import textwrap
4
5
from pathlib import Path
5
- from typing import List
6
+ from typing import List , Optional
6
7
7
8
import networkx as nx
8
- import pandas as pd
9
9
10
10
from . import report as r
11
+ from . import table_utils
11
12
from .utils import create_folder , get_relative_file_path , is_url , sort_imports
12
13
13
14
@@ -271,10 +272,6 @@ def run_report(self, output_dir: str = BASE_DIR) -> None:
271
272
[self .quarto_path , "install" , "tinytex" , "--no-prompt" ],
272
273
check = True ,
273
274
)
274
- subprocess .run (
275
- [self .quarto_path , "install" , "chromium" , "--no-prompt" ],
276
- check = True ,
277
- )
278
275
try :
279
276
subprocess .run (
280
277
args ,
@@ -712,19 +709,16 @@ def _generate_dataframe_content(self, dataframe) -> List[str]:
712
709
713
710
# Append header for DataFrame loading
714
711
dataframe_content .append (
715
- f"""```{{python}}
716
- #| label: '{ dataframe .title } { dataframe .id } '
717
- #| fig-cap: ""
718
- """
712
+ textwrap .dedent (
713
+ f"""\
714
+ ```{{python}}
715
+ #| label: '{ dataframe .title } { dataframe .id } '
716
+ #| fig-cap: ""
717
+ """
718
+ )
719
719
)
720
720
# Mapping of file extensions to read functions
721
- read_function_mapping = {
722
- r .DataFrameFormat .CSV .value_with_dot : pd .read_csv ,
723
- r .DataFrameFormat .PARQUET .value_with_dot : pd .read_parquet ,
724
- r .DataFrameFormat .TXT .value_with_dot : pd .read_table ,
725
- r .DataFrameFormat .XLS .value_with_dot : pd .read_excel ,
726
- r .DataFrameFormat .XLSX .value_with_dot : pd .read_excel ,
727
- }
721
+ read_function_mapping = table_utils .read_function_mapping
728
722
try :
729
723
# Check if the file extension matches any DataFrameFormat value
730
724
file_extension = Path (dataframe .file_path ).suffix .lower ()
@@ -740,24 +734,68 @@ def _generate_dataframe_content(self, dataframe) -> List[str]:
740
734
df_file_path = dataframe .file_path
741
735
else :
742
736
df_file_path = get_relative_file_path (
743
- dataframe .file_path , base_path = ".."
737
+ dataframe .file_path ,
744
738
)
739
+ sheet_names = None
740
+ # If the file is an Excel file, get the sheet names
741
+ if file_extension in [
742
+ r .DataFrameFormat .XLS .value_with_dot ,
743
+ r .DataFrameFormat .XLSX .value_with_dot ,
744
+ ]:
745
+ sheet_names = table_utils .get_sheet_names (df_file_path )
746
+ if len (sheet_names ) > 1 :
747
+ # If there are multiple sheets, use the first one
748
+ self .report .logger .info (
749
+ f"Multiple sheets found in the Excel file: { df_file_path } . "
750
+ f"Sheets: { sheet_names } "
751
+ )
752
+ else :
753
+ sheet_names = None
745
754
755
+ # Build the file path (URL or local file)
756
+ if is_url (dataframe .file_path ):
757
+ df_file_path = dataframe .file_path
758
+ else :
759
+ df_file_path = get_relative_file_path (
760
+ dataframe .file_path , base_path = ".."
761
+ )
746
762
# Load the DataFrame using the correct function
747
763
read_function = read_function_mapping [file_extension ]
748
764
dataframe_content .append (
749
765
f"""df = pd.{ read_function .__name__ } ('{ df_file_path .as_posix ()} ')\n """
750
766
)
751
-
752
767
# Display the dataframe
753
768
dataframe_content .extend (self ._show_dataframe (dataframe ))
754
769
770
+ # Add further sheets
771
+ if sheet_names :
772
+ for sheet_name in sheet_names [1 :]:
773
+ dataframe_content .append (f"#### { sheet_name } " )
774
+ dataframe_content .append (
775
+ textwrap .dedent (
776
+ f"""\
777
+ ```{{python}}
778
+ #| label: '{ dataframe .title } { dataframe .id } { sheet_name } '
779
+ #| fig-cap: ""
780
+ """
781
+ )
782
+ )
783
+ dataframe_content .append (
784
+ f"df = pd.{ read_function .__name__ } ('{ df_file_path .as_posix ()} ', "
785
+ f"sheet_name='{ sheet_name } ')\n "
786
+ )
787
+ # Display the dataframe
788
+ dataframe_content .extend (
789
+ self ._show_dataframe (dataframe , suffix = sheet_name )
790
+ )
791
+
755
792
except Exception as e :
756
793
self .report .logger .error (
757
794
f"Error generating content for DataFrame: { dataframe .title } . Error: { str (e )} "
758
795
)
759
796
raise
760
797
# Add caption if available
798
+ # ? Where should this come from?
761
799
if dataframe .caption :
762
800
dataframe_content .append (f">{ dataframe .caption } \n " )
763
801
@@ -787,18 +825,24 @@ def _generate_markdown_content(self, markdown) -> List[str]:
787
825
try :
788
826
# Initialize md code with common structure
789
827
markdown_content .append (
790
- f"""
791
- ```{{python}}
792
- #| label: '{ markdown .title } { markdown .id } '
793
- #| fig-cap: ""\n """
828
+ textwrap .dedent (
829
+ f"""
830
+ ```{{python}}
831
+ #| label: '{ markdown .title } { markdown .id } '
832
+ #| fig-cap: ""
833
+ """
834
+ )
794
835
)
795
836
# If the file path is a URL, generate code to fetch content via requests
796
837
if is_url (markdown .file_path ):
797
838
markdown_content .append (
798
- f"""
799
- response = requests.get('{ markdown .file_path } ')
800
- response.raise_for_status()
801
- markdown_content = response.text\n """
839
+ textwrap .dedent (
840
+ f"""\
841
+ response = requests.get('{ markdown .file_path } ')
842
+ response.raise_for_status()
843
+ markdown_content = response.text
844
+ """
845
+ )
802
846
)
803
847
else : # If it's a local file
804
848
md_rel_path = get_relative_file_path (markdown .file_path , base_path = ".." )
@@ -826,14 +870,17 @@ def _generate_markdown_content(self, markdown) -> List[str]:
826
870
)
827
871
return markdown_content
828
872
829
- def _show_dataframe (self , dataframe ) -> List [str ]:
873
+ def _show_dataframe (self , dataframe , suffix : Optional [ str ] = None ) -> List [str ]:
830
874
"""
831
875
Appends either a static image or an interactive representation of a DataFrame to the content list.
832
876
833
877
Parameters
834
878
----------
835
879
dataframe : DataFrame
836
880
The DataFrame object containing the data to display.
881
+ suffix : str, optional
882
+ A suffix to append to the DataFrame image file name like a sheet name
883
+ or another identifier (default is None).
837
884
838
885
Returns
839
886
-------
@@ -843,14 +890,19 @@ def _show_dataframe(self, dataframe) -> List[str]:
843
890
dataframe_content = []
844
891
if self .is_report_static :
845
892
# Generate path for the DataFrame image
846
- df_image = (
847
- Path (self .static_dir ) / f"{ dataframe .title .replace (' ' , '_' )} .png"
848
- )
893
+ fpath_df_image = Path (self .static_dir ) / dataframe .title .replace (" " , "_" )
894
+ if suffix :
895
+ fpath_df_image = fpath_df_image .with_stem (
896
+ fpath_df_image .stem + f"_{ suffix .replace (' ' , '_' )} "
897
+ )
898
+ fpath_df_image = fpath_df_image .with_suffix (".png" )
899
+
849
900
dataframe_content .append (
850
- f"df.dfi.export('{ Path (df_image ).relative_to ('quarto_report' ).as_posix ()} ', max_rows=10, max_cols=5, table_conversion='matplotlib')\n ```\n "
901
+ f"df.dfi.export('{ Path (fpath_df_image ).relative_to ('quarto_report' ).as_posix ()} ',"
902
+ " max_rows=10, max_cols=5, table_conversion='matplotlib')\n ```\n "
851
903
)
852
904
# Use helper method to add centered image content
853
- dataframe_content .append (self ._generate_image_content (df_image ))
905
+ dataframe_content .append (self ._generate_image_content (fpath_df_image ))
854
906
else :
855
907
# Append code to display the DataFrame interactively
856
908
dataframe_content .append (
@@ -961,10 +1013,13 @@ def _generate_component_imports(self, component: r.Component) -> List[str]:
961
1013
"import json" ,
962
1014
],
963
1015
},
964
- "dataframe " : [
1016
+ "static_dataframe " : [
965
1017
"import pandas as pd" ,
966
- "from itables import show, init_notebook_mode" ,
967
1018
"import dataframe_image as dfi" ,
1019
+ ],
1020
+ "interactive_dataframe" : [
1021
+ "import pandas as pd" ,
1022
+ "from itables import show, init_notebook_mode" ,
968
1023
"init_notebook_mode(all_interactive=True)" ,
969
1024
],
970
1025
"markdown" : ["import IPython.display as display" , "import requests" ],
@@ -980,7 +1035,10 @@ def _generate_component_imports(self, component: r.Component) -> List[str]:
980
1035
if plot_type in components_imports ["plot" ]:
981
1036
component_imports .extend (components_imports ["plot" ][plot_type ])
982
1037
elif component_type == r .ComponentType .DATAFRAME :
983
- component_imports .extend (components_imports ["dataframe" ])
1038
+ if self .is_report_static :
1039
+ component_imports .extend (components_imports ["static_dataframe" ])
1040
+ else :
1041
+ component_imports .extend (components_imports ["interactive_dataframe" ])
984
1042
elif component_type == r .ComponentType .MARKDOWN :
985
1043
component_imports .extend (components_imports ["markdown" ])
986
1044
0 commit comments