Skip to content

Commit 867d8dc

Browse files
authored
✨ make relative outputs and path work better in streamlit (#146)
* 🐛 use get_relative_file_path so that report can be generated from subfolder Now this works and can be run from the docs folder using: cd docs vuegen -c example_config_files/Basic_example_vuegen_demo_notebook_config.yaml -output_dir ../tests/report_examples/Basic_example_vuegen_demo_notebook_cfg streamlit run ../tests/report_examples/Basic_example_vuegen_demo_notebook_cfg/streamlit_report/sections/report_manager.py * 🐛 update logo path (from previous commit) - this should also bug in `split_readme.py` where the link was not updated, leading to a missing landing page! * 🐛 add logo update to testing creating from cfg file for html report * 🚧 make outputdir option for streamlit optional, move to init - outputdir only really makes sense for report generation, not execution: In the current implementation the report has to execute from the path it was generated from - should the static dir be change upon change of section_dir? * ✨ make file path relative w.r.t. to report_manager.py - currently the section_dir is set with the knowleadge of the streamlit report structure - Allows to execute report from everywhere on that operating system - folder with streamlit report cannot be copied though. * ⏪ import statement was changed accidently in test * ✨ Write README to output folder - could also contain completion message? * 🎨 remove whitespaces (W293) * 📝 add link to repository in README for streamlit report * 🎨 remove white spaces W293 * Update kernel name in notebook
1 parent d8ecd52 commit 867d8dc

File tree

27 files changed

+1155
-65
lines changed

27 files changed

+1155
-65
lines changed

.github/workflows/cdci.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ jobs:
118118
echo "Error: One or more protected files have been modified."
119119
exit 1
120120
fi
121+
- name: streamlit report based on predefined config file
122+
run: |
123+
cd docs
124+
vuegen -c example_config_files/Basic_example_vuegen_demo_notebook_config.yaml -output_dir ../tests/report_examples/Basic_example_vuegen_demo_notebook_cfg
125+
# Check for changes
126+
if git diff ../tests/report_examples | grep .; then
127+
echo "Error: One or more protected files have been modified."
128+
exit 1
129+
fi
121130
- name: check streamlit report files for chatbot API
122131
run: |
123132
vuegen -c docs/example_config_files/Chatbot_example_config.yaml -output_dir tests/report_examples/chat_bot
@@ -208,7 +217,7 @@ jobs:
208217
- name: Build executable
209218
run: |
210219
cd gui
211-
pyinstaller -n vuegen_gui --onefile --windowed --collect-all pyvis --collect-all streamlit --collect-all st_aggrid --collect-all customtkinter --collect-all quarto_cli --collect-all plotly --collect-all _plotly_utils --collect-all traitlets --collect-all referencing --collect-all rpds --collect-all tenacity --collect-all pandas --collect-all numpy --collect-all matplotlib --collect-all openpyxl --collect-all xlrd --collect-all nbformat --collect-all nbclient --collect-all altair --collect-all itables --collect-all kaleido --collect-all pyarrow --collect-all dataframe_image --collect-all narwhals --collect-all PIL --collect-all vl_convert --collect-all typing-extensions --add-data ../docs/example_data/Basic_example_vuegen_demo_notebook:example_data/Basic_example_vuegen_demo_notebook --add-data ../docs/images/vuegen_logo.png:. app.py
220+
pyinstaller -n vuegen_gui --onefile --windowed --collect-all pyvis --collect-all streamlit --collect-all st_aggrid --collect-all customtkinter --collect-all quarto_cli --collect-all plotly --collect-all _plotly_utils --collect-all traitlets --collect-all referencing --collect-all rpds --collect-all tenacity --collect-all pandas --collect-all numpy --collect-all matplotlib --collect-all openpyxl --collect-all xlrd --collect-all nbformat --collect-all nbclient --collect-all altair --collect-all itables --collect-all kaleido --collect-all pyarrow --collect-all dataframe_image --collect-all narwhals --collect-all PIL --collect-all vl_convert --collect-all typing-extensions --add-data ../docs/example_data/Basic_example_vuegen_demo_notebook:example_data/Basic_example_vuegen_demo_notebook --add-data ../docs/images/logo/vuegen_logo.png:. app.py
212221
# --windowed only for mac, see:
213222
# https://pyinstaller.org/en/stable/usage.html#building-macos-app-bundles
214223
# 'Under macOS, PyInstaller always builds a UNIX executable in dist.'

docs/example_config_files/Basic_example_vuegen_demo_notebook_config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
report:
22
title: Basic Example Vuegen Demo Notebook
33
description: A general description of the report.
4-
graphical_abstract: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.png
5-
logo: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.png
4+
graphical_abstract: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/logo/vuegen_logo.png
5+
logo: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/logo/vuegen_logo.png
66
sections:
77
- title: Plots
88
description: This section contains example plots.

docs/example_data/Basic_example_vuegen_demo_notebook/5_Markdown/1_All_markdown/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<!-- <div align="center">
2-
<img width="300px" src="images/vuegen_logo.svg">
2+
<img width="300px" src="images/logo/vuegen_logo.svg">
33
</div> -->
44

5-
![VueGen Logo](https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.png)
5+
![VueGen Logo](https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/logo/vuegen_logo.png)
66

77
<p align="center">
88
VueGen is a Python library that automates the creation of scientific reports.

docs/split_readme.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# Mapping section titles to their corresponding filenames
55
SECTION_MAPPING = {
6-
"![VueGen Logo](https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.svg)": "home_page.md",
6+
"![VueGen Logo](https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/HEAD/docs/images/logo/vuegen_logo.svg)": "home_page.md",
77
"About the project": "about.md",
88
"Installation": "installation.md",
99
"Execution": "execution.md",

docs/vuegen_basic_case_study.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@
273273
},
274274
"outputs": [],
275275
"source": [
276-
"vuegen_logo_path = \"https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.svg\"\n",
276+
"vuegen_logo_path = \"https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/logo/vuegen_logo.png\"\n",
277277
"\n",
278278
"# Load the YAML file\n",
279279
"print(\n",
@@ -457,7 +457,7 @@
457457
"name": "python",
458458
"nbconvert_exporter": "python",
459459
"pygments_lexer": "ipython3",
460-
"version": "3.12.6"
460+
"version": "3.12.9"
461461
}
462462
},
463463
"nbformat": 4,

docs/vuegen_basic_case_study_configfile.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ The [configuration file](https://github.com/Multiomics-Analytics-Group/vuegen/bl
66
report:
77
title: Basic Example Vuegen Demo Notebook
88
description: A general description of the report.
9-
graphical_abstract: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.png
10-
logo: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.png
9+
graphical_abstract: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/logo/vuegen_logo.png
10+
logo: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/logo/vuegen_logo.png
1111
sections:
1212
- title: Plots
1313
description: This section contains example plots.

gui/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ bundle:
3939
--collect-all vl_convert \
4040
--collect-all typing-extensions \
4141
--add-data ../docs/example_data/Basic_example_vuegen_demo_notebook:example_data/Basic_example_vuegen_demo_notebook \
42-
--add-data ../docs/images/vuegen_logo.png:. \
42+
--add-data ../docs/images/logo/vuegen_logo.png:. \
4343
app.py
4444

4545

src/vuegen/report_generator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,10 @@ def get_report(
108108
report_type=report_type,
109109
streamlit_autorun=streamlit_autorun,
110110
static_dir=static_files_dir,
111+
sections_dir=sections_dir,
111112
)
112-
st_report.generate_report(output_dir=sections_dir)
113-
st_report.run_report(output_dir=sections_dir)
113+
st_report.generate_report()
114+
st_report.run_report()
114115
else:
115116
# Check if Quarto is installed
116117
if shutil.which("quarto") is None and not hasattr(

src/vuegen/streamlit_reportview.py

Lines changed: 90 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def __init__(
5050
report_type: r.ReportType,
5151
streamlit_autorun: bool = False,
5252
static_dir: str = STATIC_FILES_DIR,
53+
sections_dir: str = SECTIONS_DIR,
5354
):
5455
"""Initialize ReportView with the report and report type.
5556
@@ -86,8 +87,9 @@ def __init__(
8687
}
8788

8889
self.static_dir = static_dir
90+
self.section_dir = sections_dir
8991

90-
def generate_report(self, output_dir: str = SECTIONS_DIR) -> None:
92+
def generate_report(self, output_dir: str = None) -> None:
9193
"""
9294
Generates the Streamlit report and creates Python files for each section
9395
and its subsections and plots.
@@ -98,6 +100,10 @@ def generate_report(self, output_dir: str = SECTIONS_DIR) -> None:
98100
The folder where the generated report files will be saved
99101
(default is SECTIONS_DIR).
100102
"""
103+
if output_dir is not None:
104+
# ? does this imply changes to the static dir
105+
self.section_dir = Path(output_dir).resolve()
106+
output_dir = Path(self.section_dir)
101107
self.report.logger.debug(
102108
"Generating '%s' report in directory: '%s'", self.report_type, output_dir
103109
)
@@ -274,6 +280,29 @@ def generate_report(self, output_dir: str = SECTIONS_DIR) -> None:
274280

275281
# Create Python files for each section and its subsections and plots
276282
self._generate_sections(output_dir=output_dir)
283+
284+
# Save README.md to the output directory
285+
fpath = self.section_dir.parent / "README.md"
286+
with open(fpath, "w", encoding="utf-8") as f:
287+
288+
f.write(
289+
textwrap.dedent(
290+
f"""\
291+
# Streamlit Report
292+
293+
This report was generated using the Vuegen library:
294+
https://github.com/Multiomics-Analytics-Group/vuegen
295+
296+
Executed from: `{Path.cwd()}`
297+
298+
Written to: `{self.section_dir.resolve()}`
299+
300+
Folder cannot be moved from above path, but can be executed
301+
from anywhere on the system.
302+
"""
303+
)
304+
)
305+
277306
except Exception as e:
278307
self.report.logger.error(
279308
"An error occurred while generating the report: %s",
@@ -282,7 +311,7 @@ def generate_report(self, output_dir: str = SECTIONS_DIR) -> None:
282311
)
283312
raise
284313

285-
def run_report(self, output_dir: str = SECTIONS_DIR) -> None:
314+
def run_report(self, output_dir: str = None) -> None:
286315
"""
287316
Runs the generated Streamlit report.
288317
@@ -291,6 +320,9 @@ def run_report(self, output_dir: str = SECTIONS_DIR) -> None:
291320
output_dir : str, optional
292321
The folder where the report was generated (default is SECTIONS_DIR).
293322
"""
323+
if output_dir is not None:
324+
self.report.logger.warning("The output_dir parameter is deprecated.")
325+
output_dir = Path(self.section_dir)
294326
if self.streamlit_autorun:
295327
self.report.logger.info(
296328
"Running '%s' %s report.", self.report.title, self.report_type
@@ -657,10 +689,17 @@ def _generate_plot_content(self, plot) -> List[str]:
657689
# If the file path is a URL, keep the file path as is
658690
if is_url(plot.file_path):
659691
plot_file_path = plot.file_path
692+
plot_content.append(f"plot_file_path = '{plot_file_path}'")
660693
else: # If it's a local file
661-
plot_file_path = get_relative_file_path(plot.file_path).as_posix()
694+
plot_file_path = get_relative_file_path(
695+
plot.file_path, relative_to=self.section_dir
696+
).as_posix()
697+
plot_content.append(
698+
f"plot_file_path = (section_dir / '{plot_file_path}')"
699+
".resolve().as_posix()"
700+
)
662701
plot_content.append(
663-
f"\nst.image('{plot_file_path}', "
702+
"st.image(plot_file_path,"
664703
f" caption='{plot.caption}', use_column_width=True)\n"
665704
)
666705
elif plot.plot_type == r.PlotType.PLOTLY:
@@ -699,11 +738,14 @@ def _generate_plot_content(self, plot) -> List[str]:
699738
)
700739
)
701740
else:
702-
fpath = Path(html_plot_file).resolve().relative_to(Path.cwd())
741+
fpath = get_relative_file_path(
742+
html_plot_file, relative_to=self.section_dir
743+
).as_posix()
703744
plot_content.append(
704745
textwrap.dedent(
705746
f"""
706-
with open('{fpath}', 'r') as html_file:
747+
file_path = (section_dir / '{fpath}').resolve().as_posix()
748+
with open(file_path, 'r') as html_file:
707749
html_content = html_file.read()
708750
"""
709751
)
@@ -770,10 +812,13 @@ def _generate_plot_code(self, plot) -> str:
770812
plot_json = json.loads(response.text)\n"""
771813
)
772814
else: # If it's a local file
773-
plot_rel_path = get_relative_file_path(plot.file_path)
815+
plot_rel_path = get_relative_file_path(
816+
plot.file_path, relative_to=self.section_dir
817+
).as_posix()
774818
plot_code = textwrap.dedent(
775819
f"""
776-
with open('{plot_rel_path.as_posix()}', 'r') as plot_file:
820+
file_path = (section_dir / '{plot_rel_path}').resolve().as_posix()
821+
with open(file_path, 'r') as plot_file:
777822
plot_json = json.load(plot_file)\n"""
778823
)
779824

@@ -864,11 +909,14 @@ def _generate_dataframe_content(self, dataframe) -> List[str]:
864909
sheet_names = table_utils.get_sheet_names(df_file_path.as_posix())
865910
if len(sheet_names) > 1:
866911
# If there are multiple sheets, ask the user to select one
867-
fpath = df_file_path.as_posix()
912+
fpath = get_relative_file_path(
913+
dataframe.file_path, relative_to=self.section_dir
914+
).as_posix()
868915
dataframe_content.append(
869916
textwrap.dedent(
870917
f"""\
871-
sheet_names = table_utils.get_sheet_names("{fpath}")
918+
file_path = (section_dir / '{fpath}').resolve().as_posix()
919+
sheet_names = table_utils.get_sheet_names(file_path)
872920
selected_sheet = st.selectbox("Select a sheet to display",
873921
options=sheet_names,
874922
)
@@ -877,18 +925,27 @@ def _generate_dataframe_content(self, dataframe) -> List[str]:
877925
)
878926

879927
# Load the DataFrame using the correct function
880-
read_function = read_function_mapping[file_extension]
928+
df_file_path = get_relative_file_path(
929+
dataframe.file_path, relative_to=self.section_dir
930+
).as_posix()
931+
read_function = read_function_mapping[file_extension].__name__
881932
if file_extension in [
882933
r.DataFrameFormat.XLS.value_with_dot,
883934
r.DataFrameFormat.XLSX.value_with_dot,
884935
]:
885936
dataframe_content.append(
886-
f"df = pd.{read_function.__name__}('{df_file_path.as_posix()}',"
887-
" sheet_name=selected_sheet)\n"
937+
textwrap.dedent(
938+
f"""\
939+
file_path = (section_dir / '{df_file_path}').resolve()
940+
df = pd.{read_function}(file_path, sheet_name=selected_sheet)
941+
"""
942+
)
888943
)
889944
else:
890945
dataframe_content.append(
891-
f"df = pd.{read_function.__name__}('{df_file_path.as_posix()}')\n"
946+
f"file_path = (section_dir / '{df_file_path}'"
947+
").resolve().as_posix()\n"
948+
f"df = pd.{read_function}(file_path)\n"
892949
)
893950
# ! Alternative to select box: iterate over sheets in DataFrame
894951
# Displays a DataFrame using AgGrid with configurable options.
@@ -982,11 +1039,15 @@ def _generate_markdown_content(self, markdown) -> List[str]:
9821039
)
9831040
)
9841041
else: # If it's a local file
985-
md_rel_path = get_relative_file_path(markdown.file_path)
1042+
md_rel_path = get_relative_file_path(
1043+
markdown.file_path, relative_to=self.section_dir
1044+
).as_posix()
1045+
9861046
markdown_content.append(
9871047
textwrap.dedent(
9881048
f"""
989-
with open('{md_rel_path.as_posix()}', 'r') as markdown_file:
1049+
file_path = (section_dir / '{md_rel_path}').resolve().as_posix()
1050+
with open(file_path, 'r') as markdown_file:
9901051
markdown_content = markdown_file.read()
9911052
"""
9921053
)
@@ -1052,13 +1113,16 @@ def _generate_html_content(self, html) -> List[str]:
10521113
)
10531114
)
10541115
else: # If it's a local file
1055-
html_rel_path = get_relative_file_path(html.file_path).as_posix()
1116+
html_rel_path = get_relative_file_path(
1117+
html.file_path, relative_to=self.section_dir
1118+
).as_posix()
10561119
html_content.append(
10571120
textwrap.dedent(
1058-
f"""
1059-
with open('{html_rel_path}', 'r', encoding='utf-8') as f:
1060-
html_content = f.read()
1061-
"""
1121+
f"""\
1122+
file_path = (section_dir / '{html_rel_path}').resolve().as_posix()
1123+
with open(file_path, 'r', encoding='utf-8') as f:
1124+
html_content = f.read()
1125+
"""
10621126
)
10631127
)
10641128

@@ -1376,7 +1440,11 @@ def _generate_component_imports(self, component: r.Component) -> List[str]:
13761440
}
13771441

13781442
component_type = component.component_type
1379-
component_imports = ["import streamlit as st"]
1443+
component_imports = [
1444+
"import streamlit as st",
1445+
"from pathlib import Path",
1446+
"section_dir = Path(__file__).resolve().parent.parent",
1447+
]
13801448

13811449
# Add relevant imports based on component type and visualization tool
13821450
if component_type == r.ComponentType.PLOT:

0 commit comments

Comments
 (0)