diff --git a/.github/workflows/cdci.yml b/.github/workflows/cdci.yml new file mode 100644 index 0000000..1a9b233 --- /dev/null +++ b/.github/workflows/cdci.yml @@ -0,0 +1,33 @@ +name: Python package + +on: + push: + branches: [main] + pull_request: + branches: [main] + release: + types: [published] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' # caching pip dependencies + cache-dependency-path: '**/pyproject.toml' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + pip install -e . + - name: Run tests + run: python -m pytest tests + diff --git a/README.md b/README.md index f3bbb80..de1ac85 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,46 @@ Also, the class diagram for the project is presented below to illustrate the arc

## Installation -...... + +You can install the package for development from this repository by running the following command: + +```bash +pip install -e path/to/vuegen # specify location +pip install -e . # in case you pwd is in the vuegen directory +``` + +### Quatro installation + +Install quatro with vueguen if you don't have it globally installed. + +```bash +pip install -e ".[quarto]" # specify location +``` + +if you use conda a conda environement you can install quatro from the conda-forge channel +in case it did not work. + +```bash +conda install -c conda-forge quarto +``` + +Test your quarto installation by running the following command: + +```bash +quarto check +``` + ## Execution -``` shell -python vuegen/main.py --config report_config_micw2graph.yaml --report_type streamlit + +```bash +python vuegen/main.py --config example_data/MicW2Graph/report_config_micw2graph.yaml --report_type streamlit ``` + The current report types are streamlit, html, pdf, docx, odt, revealjs, pptx, and jupyter. ## Contact If you have comments or suggestions about this project, you can [open an issue][issues] in this repository. [issues]: https://github.com/Multiomics-Analytics-Group/vuegen/issues/new -[streamlit]: https://streamlit.io/ \ No newline at end of file +[streamlit]: https://streamlit.io/ diff --git a/example_data/MicW2Graph/report_config_micw2graph.yaml b/example_data/MicW2Graph/report_config_micw2graph.yaml index e9af6b4..e4aa322 100644 --- a/example_data/MicW2Graph/report_config_micw2graph.yaml +++ b/example_data/MicW2Graph/report_config_micw2graph.yaml @@ -9,8 +9,8 @@ report: infer potential interactions among microorganisms through microbial association networks (MANs). MicW2Graph enables the investigation of research questions related to WWT, focusing on aspects such as microbial connections, community memberships, and potential ecological functions. - graphical_abstract: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/Methods_MicW2Graph.png - logo: /Users/asaru/Documents/DTU/MoNA/vuegen/docs/images/vuegen_logo.png + graphical_abstract: example_data/MicW2Graph/Methods_MicW2Graph.png + logo: docs/images/vuegen_logo.png sections: - title: Exploratory Data Analysis subsections: @@ -25,7 +25,7 @@ sections: - title: Multiline plot (altair) component_type: plot plot_type: altair - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/2_multilineplot_altair.json + file_path: example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/2_multilineplot_altair.json - title: Abundance data for all studies (csv) component_type: dataframe file_path: https://raw.githubusercontent.com/sayalaruano/report_generator_st_cloud_test/main/example_data/MicW2Graph/abundance_data_allbiomes.csv @@ -33,7 +33,7 @@ sections: delimiter: ',' - title: Abundance data for all studies (xls) component_type: dataframe - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/4_abundance_data_allbiomes.xls + file_path: example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/4_abundance_data_allbiomes.xls file_format: xls delimiter: ',' - title: Sample data @@ -41,18 +41,18 @@ sections: - title: Number of samples per study (png) component_type: plot plot_type: static - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/3_number_samples_per_study.png + file_path: example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/3_number_samples_per_study.png - title: Sampling countries for all studies (plotly) component_type: plot plot_type: plotly - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/1_pie_plot_countries_plotly.json + file_path: example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/1_pie_plot_countries_plotly.json - title: Sample data for all studies (parquet) component_type: dataframe - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/4_sample_info_allbiomes.parquet + file_path: example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/4_sample_info_allbiomes.parquet file_format: parquet - title: Sample data for all studies (txt) component_type: dataframe - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/5_sample_info_allbiomes.txt + file_path: example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/5_sample_info_allbiomes.txt file_format: txt - title: Extra information components: @@ -66,17 +66,17 @@ sections: - title: Network1 (graphml) component_type: plot plot_type: interactive_network - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/1_Network_visualization1/1_man_example.graphml + file_path: example_data/MicW2Graph/2_Microbial_Association_Networks/1_Network_visualization1/1_man_example.graphml - title: Network Visualization2 components: - title: Network2 (edge list csv) component_type: plot plot_type: interactive_network csv_network_format: edgelist - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/2_Network_visualization2/1_man_example.csv + file_path: example_data/MicW2Graph/2_Microbial_Association_Networks/2_Network_visualization2/1_man_example.csv - title: Edge list (csv) component_type: dataframe - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/2_Network_visualization2/1_man_example.csv + file_path: example_data/MicW2Graph/2_Microbial_Association_Networks/2_Network_visualization2/1_man_example.csv file_format: csv delimiter: ',' caption: This is the edge list of the network @@ -85,13 +85,13 @@ sections: - title: Network3 (cyjs) component_type: plot plot_type: interactive_network - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/3_Network_visualization3/1_man_example.cyjs + file_path: example_data/MicW2Graph/2_Microbial_Association_Networks/3_Network_visualization3/1_man_example.cyjs - title: Network Visualization4 components: - title: Network4 (html) component_type: plot plot_type: interactive_network - file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/4_Network_visualization4/1_ckg_network.html + file_path: example_data/MicW2Graph/2_Microbial_Association_Networks/4_Network_visualization4/1_ckg_network.html - title: Network Visualization5 components: - title: Network5 (remote html) diff --git a/pyproject.toml b/pyproject.toml index d6233a4..484fd2c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ [tool.poetry] -name = "report-generator" +name = "vuegen" version = "0.1.0" description = "Module to generate automatic web interface reports with visualizations" authors = ["MoNA group"] license = "MIT" readme = "README.md" +repository = "https://github.com/Multiomics-Analytics-Group/vuegen" [tool.poetry.dependencies] python = ">=3.9,<3.9.7 || >3.9.7,<4.0" @@ -25,11 +26,20 @@ itables = "^2.2.2" kaleido = "0.2.0" vl-convert-python = "^1.7.0" dataframe-image = "^0.2.6" - +strenum = { version = "^0.4.15", python = "<3.11" } [tool.poetry.group.dev.dependencies] ipykernel = "^6.29.5" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + +# https://stackoverflow.com/a/60990574/9684872 +[tool.poetry.extras] +streamlit = ["streamlit"] +quarto = ["quarto", "ipykernel"] + +# [project.scripts] +# my-script = "vuegen.main:main" diff --git a/tests/test_module_imports.py b/tests/test_module_imports.py new file mode 100644 index 0000000..64657e4 --- /dev/null +++ b/tests/test_module_imports.py @@ -0,0 +1,12 @@ + +def test_imports(): + import vuegen + import vuegen.main + import vuegen.quarto_reportview + import vuegen.report + import vuegen.report_generator + import vuegen.streamlit_reportview + import vuegen.utils + + assert vuegen.__version__ + diff --git a/vuegen/__init__.py b/vuegen/__init__.py new file mode 100644 index 0000000..5986200 --- /dev/null +++ b/vuegen/__init__.py @@ -0,0 +1,3 @@ +from importlib.metadata import version + +__version__ = version("vuegen") diff --git a/vuegen/config_manager.py b/vuegen/config_manager.py index d06f9f5..4ad12ac 100644 --- a/vuegen/config_manager.py +++ b/vuegen/config_manager.py @@ -1,5 +1,6 @@ -import report as r -from utils import get_logger, assert_enum_value +from . import report as r +from .utils import assert_enum_value, get_logger + class ConfigManager: """ @@ -260,4 +261,4 @@ def _create_chatbot_component(self, component_data: dict) -> r.ChatBot: caption = component_data.get('caption'), headers = component_data.get('headers'), params = component_data.get('params') - ) \ No newline at end of file + ) diff --git a/vuegen/main.py b/vuegen/main.py index b163954..9d1af50 100644 --- a/vuegen/main.py +++ b/vuegen/main.py @@ -1,5 +1,5 @@ -import report_generator -from utils import get_logger, load_yaml_config, get_args +from vuegen import report_generator +from vuegen.utils import get_args, get_logger, load_yaml_config if __name__ == '__main__': # Parse command-line arguments diff --git a/vuegen/quarto_reportview.py b/vuegen/quarto_reportview.py index 954e9e2..3cf829a 100644 --- a/vuegen/quarto_reportview.py +++ b/vuegen/quarto_reportview.py @@ -1,10 +1,13 @@ import os import subprocess -import report as r from typing import List + import networkx as nx import pandas as pd -from utils import create_folder, is_url + +from . import report as r +from .utils import create_folder, is_url + class QuartoReportView(r.ReportView): """ @@ -117,6 +120,9 @@ def run_report(self, output_dir: str = BASE_DIR) -> None: except subprocess.CalledProcessError as e: self.report.logger.error(f"Error running '{self.report.title}' {self.report_type} report: {str(e)}") raise + except FileNotFoundError as e: + self.report.logger.error(f"Quarto is not installed. Please install Quarto to run the report: {str(e)}") + raise def _create_yaml_header(self) -> str: """ diff --git a/vuegen/report.py b/vuegen/report.py index a85830b..7c8224d 100644 --- a/vuegen/report.py +++ b/vuegen/report.py @@ -1,17 +1,25 @@ +import json +import logging import os from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import ClassVar -from enum import StrEnum, auto -from typing import List, Optional +from enum import auto + +try: + from enum import StrEnum +except ImportError: + from strenum import StrEnum + +from typing import ClassVar, List, Optional + +import matplotlib.pyplot as plt import networkx as nx import pandas as pd -import logging import requests -import json -import matplotlib.pyplot as plt from pyvis.network import Network -from utils import cyjs_to_networkx, pyvishtml_to_networkx, fetch_file_stream + +from .utils import cyjs_to_networkx, fetch_file_stream, pyvishtml_to_networkx + class ReportType(StrEnum): STREAMLIT = auto() @@ -736,4 +744,4 @@ def _generate_subsection(self, subsection: Subsection) -> tuple[List[str], List[ - list of subsection content lines (List[str]) - list of imports for the subsection (List[str]) """ - pass \ No newline at end of file + pass diff --git a/vuegen/report_generator.py b/vuegen/report_generator.py index 80c11c9..9975d99 100644 --- a/vuegen/report_generator.py +++ b/vuegen/report_generator.py @@ -1,10 +1,12 @@ -from streamlit_reportview import StreamlitReportView -from quarto_reportview import QuartoReportView -from config_manager import ConfigManager -from utils import assert_enum_value -from report import ReportType import logging +from .config_manager import ConfigManager +from .quarto_reportview import QuartoReportView +from .report import ReportType +from .streamlit_reportview import StreamlitReportView +from .utils import assert_enum_value + + def get_report(config: dict, report_type: str, logger: logging.Logger) -> None: """ Generate and run a report based on the specified engine. @@ -45,4 +47,4 @@ def get_report(config: dict, report_type: str, logger: logging.Logger) -> None: report_type=report_type ) quarto_report.generate_report() - quarto_report.run_report() \ No newline at end of file + quarto_report.run_report() diff --git a/vuegen/streamlit_reportview.py b/vuegen/streamlit_reportview.py index a64b178..76f54be 100644 --- a/vuegen/streamlit_reportview.py +++ b/vuegen/streamlit_reportview.py @@ -1,9 +1,12 @@ -import report as r import os import subprocess from typing import List + import pandas as pd -from utils import create_folder, is_url + +from . import report as r +from .utils import create_folder, is_url + class StreamlitReportView(r.WebAppReportView): """ @@ -658,4 +661,4 @@ def _generate_component_imports(self, component: r.Component) -> List[str]: component_imports.extend(components_imports['chatbot']) # Return the list of import statements - return component_imports \ No newline at end of file + return component_imports diff --git a/vuegen/utils.py b/vuegen/utils.py index 69429ed..6a6423e 100644 --- a/vuegen/utils.py +++ b/vuegen/utils.py @@ -1,18 +1,27 @@ +from __future__ import annotations + +import argparse +import json +import logging import os import sys -import yaml from datetime import datetime -import logging -import argparse -import requests -import networkx as nx -import json + +try: + from enum import StrEnum +except ImportError: + from strenum import StrEnum + from io import StringIO -from enum import StrEnum from typing import Type -from bs4 import BeautifulSoup from urllib.parse import urlparse +import networkx as nx +import requests +import yaml +from bs4 import BeautifulSoup + + ## CHECKS def check_path(filepath: str) -> bool: """ @@ -176,7 +185,7 @@ def get_args(prog_name: str, others: dict = {}) -> argparse.Namespace: "-rt", "--report_type", type=str, - default=None, + default='streamlit', # this is not a valid default help="Type of the report to generate (streamlit, html, pdf, docx, odt, revealjs, pptx, or jupyter)." )