diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a25fb6ed..edd5b778 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install the project - run: uv sync --all-extras --group format --group test --group docs --group external-integration + run: uv sync --all-extras - name: Run tests run: uv run pytest tests --benchmark-json output.json diff --git a/.github/workflows/create-release-candidate.yml b/.github/workflows/create-release-candidate.yml index 171204bb..2e0714b1 100644 --- a/.github/workflows/create-release-candidate.yml +++ b/.github/workflows/create-release-candidate.yml @@ -31,7 +31,7 @@ jobs: - name: Install project run: | - uv sync --all-extras --group format --group test --group docs --group external-integration + uv sync --all-extras - name: Bump version id: bump diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e01f6d0f..528b21c8 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -28,7 +28,7 @@ jobs: python-version-file: "pyproject.toml" - name: Install the project - run: uv sync --all-extras --group format --group test --group docs --group external-integration + run: uv sync --all-extras - name: Build run: uv build diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index 8befb942..8800115c 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -25,7 +25,7 @@ jobs: - name: Install the project run: | - uv sync --all-extras --group format --group test --group docs --group external-integration + uv sync --all-extras uv pip install ipykernel sudo apt-get install pandoc diff --git a/docs/source/developer_guide/developer_installation.rst b/docs/source/developer_guide/developer_installation.rst index f93ec601..7e3cbd40 100644 --- a/docs/source/developer_guide/developer_installation.rst +++ b/docs/source/developer_guide/developer_installation.rst @@ -38,7 +38,7 @@ virtual environment: .. code-block:: bash - uv sync --all-extras --all-groups + uv sync --all-extras The virtual environment is stored in the :code:`PyProBE/.venv` directory inside your and can be activated with :code:`source .venv/bin/activate`. @@ -58,38 +58,13 @@ This will create a kernel that you can select within VSCode in the usual way. this method results in dependency conflicts: 1. Create and activate a virtual environment. - - .. tabs:: - .. tab:: venv - In your working directory: - - .. code-block:: bash - - python -m venv venv - source .venv/bin/activate - - .. tab:: conda - - In any directory: - - .. code-block:: bash - - conda create -n pyprobe python=3.12 - conda activate pyprobe - - 2. Install the developer dependencies: - - .. code-block:: bash - - cd /path/to/your/directory/PyProBE - pip install -r requirements-dev.txt - - 3. Install PyProBE as a package into your virtual environment: + 2. Install PyProBE as a package into your virtual environment with the developer + dependencies: .. code-block:: bash - pip install -e . + pip install -e '.[dev, docs]' The :code:`-e` flag installs in "editable" mode, which means changes that you make to the code will be automatically reflected in the package inside your diff --git a/docs/source/user_guide/installation.rst b/docs/source/user_guide/installation.rst index dcbb40c7..71e49cbb 100644 --- a/docs/source/user_guide/installation.rst +++ b/docs/source/user_guide/installation.rst @@ -59,6 +59,16 @@ The steps to install PyProBE are as follows: pip install PyProBE-Data + Optional dependencies can be added to the installation as follows: + + .. code-block:: bash + + pip install 'PyProBE-Data[hvplot]' + + If a method uses an optional dependency, it will be detailed in the + :doc:`api documentation `. If these methods is run without their dependencies + installed, they will return an error. + 3. You can create a new python script or jupyter notebook to process your data. You can import PyProBE into your script as follows: diff --git a/pyprobe/plot.py b/pyprobe/plot.py index e84ca27c..7f875bf9 100644 --- a/pyprobe/plot.py +++ b/pyprobe/plot.py @@ -70,7 +70,8 @@ def __getattr__(self, _: Any) -> None: """Raise an ImportError if seaborn is not installed.""" raise ImportError( "Optional dependency 'seaborn' is not installed. Please install by " - "running 'pip install seaborn'." + "running 'pip install seaborn' or installing PyProBE with seaborn " + "as an optional dependency: `pip install 'PyProBE-Data[seaborn]'." ) return SeabornWrapper() @@ -124,11 +125,17 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: seaborn = _create_seaborn_wrapper() """A wrapped version of the seaborn package. +Requires the seaborn package to be installed as an optional dependency. You can install +it with PyProBE by running :code:`pip install 'PyProBE-Data[seaborn]'`, or install it +seperately with :code:`pip install seaborn`. + This version of seaborn is modified to work with PyProBE Result objects. All functions from the original seaborn package are available in this version. Where seaborn functions accept a 'data' argument, a PyProBE Result object can be passed instead of a pandas DataFrame. For example: +.. code-block:: python + from pyprobe.plot import seaborn as sns result = cell.procedure['Sample'] diff --git a/pyprobe/result.py b/pyprobe/result.py index a359dcef..e925f6b1 100644 --- a/pyprobe/result.py +++ b/pyprobe/result.py @@ -237,21 +237,27 @@ def hvplot(self, *args: Any, **kwargs: Any) -> None: data_to_plot = _retrieve_relevant_columns(self, args, kwargs) return data_to_plot.hvplot(*args, **kwargs) - hvplot.__doc__ = ( - "HvPlot is a library for creating fast and interactive plots.\n\n" - "The default backend is bokeh, which can be changed by setting the backend " - "with :code:`hvplot.extension('matplotlib')` or " - ":code:`hvplot.extension('plotly')`.\n\n" + (hvplot.__doc__ or "") - ) else: def hvplot(self, *args: Any, **kwargs: Any) -> None: """Wrapper for plotting using the hvplot library.""" raise ImportError( "Optional dependency hvplot is not installed. Please install it via " - "'pip install hvplot'." + "'pip install hvplot' or by installing PyProBE with hvplot as an " + "optional dependency: pip install 'PyProBE-Data[hvplot]'." ) + hvplot.__doc__ = ( + "HvPlot is a library for creating fast and interactive plots.\n\n" + "This method requires the hvplot library to be installed as an optional " + "dependency. You can install it with PyProBE by running " + ":code:`pip install 'PyProBE-Data[hvplot]'`, or install it seperately with " + ":code:`pip install hvplot`.\n\n" + "The default backend is bokeh, which can be changed by setting the backend " + "with :code:`hvplot.extension('matplotlib')` or " + ":code:`hvplot.extension('plotly')`.\n\n" + (hvplot.__doc__ or "") + ) + def _get_data_subset(self, *column_names: str) -> pl.DataFrame: """Return a subset of the data with the specified columns. diff --git a/pyproject.toml b/pyproject.toml index 7cef6b79..3332935e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,9 +31,7 @@ hvplot = [ seaborn = [ "seaborn>=0.13.2", ] - -[dependency-groups] -format = [ +dev = [ "black>=24.10.0", "flake8>=7.1.1", "flake8-docstrings>=1.7.0", @@ -41,8 +39,6 @@ format = [ "mypy>=1.14.1", "pre-commit>=4.0.1", "python-semantic-release>=9.15.2", -] -test = [ "nbmake>=1.5.5", "pytest>=8.3.4", "pytest-benchmark>=5.1.0", @@ -52,6 +48,7 @@ test = [ "types-deprecated>=1.2.15.20241117", "types-pyyaml>=6.0.12.20241230", "types-toml>=0.10.8.20240310", + "xlsxwriter>=3.2.0", ] docs = [ "autodoc-pydantic>=2.2.0", @@ -61,9 +58,8 @@ docs = [ "sphinx-design>=0.6.1", "sphinx-tabs>=3.4.7", "sphinxcontrib-bibtex>=2.6.3", - "xlsxwriter>=3.2.0", ] -external-integration = [ +pybamm = [ "pybamm>=24.1", ] diff --git a/tests/test_cell.py b/tests/test_cell.py index 1e1d4670..3bfb9e15 100644 --- a/tests/test_cell.py +++ b/tests/test_cell.py @@ -5,7 +5,6 @@ import shutil import polars as pl -import pybamm import pytest from numpy.testing import assert_array_equal from polars.testing import assert_frame_equal @@ -146,18 +145,25 @@ def add_procedure(): return cell_instance.add_procedure(title, input_path, file_name) benchmark(add_procedure) - assert_frame_equal(cell_instance.procedure[title].data, procedure_fixture.data) + assert_frame_equal( + cell_instance.procedure[title].data, + procedure_fixture.data, + check_column_order=False, + ) cell_instance.add_procedure( "Test_custom", input_path, file_name, readme_name="README_total_steps.yaml" ) assert_frame_equal( - cell_instance.procedure["Test_custom"].data, procedure_fixture.data + cell_instance.procedure["Test_custom"].data, + procedure_fixture.data, + check_column_order=False, ) def test_import_pybamm_solution(benchmark): """Test the import_pybamm_solution method.""" + pybamm = pytest.importorskip("pybamm") parameter_values = pybamm.ParameterValues("Chen2020") spm = pybamm.lithium_ion.SPM() experiment = pybamm.Experiment( diff --git a/tests/test_plot.py b/tests/test_plot.py index 94a8003a..3953a3b8 100644 --- a/tests/test_plot.py +++ b/tests/test_plot.py @@ -5,7 +5,6 @@ import polars as pl import polars.testing as pl_testing import pytest -import seaborn as _sns from plotly.express.colors import sample_colorscale from sklearn.preprocessing import minmax_scale @@ -122,6 +121,7 @@ def test_retrieve_relevant_columns_with_unit_conversion(): def test_seaborn_wrapper_creation(): """Test basic seaborn wrapper creation.""" + pytest.importorskip("seaborn") wrapper = plot._create_seaborn_wrapper() assert wrapper is not None assert isinstance(wrapper, object) @@ -129,6 +129,7 @@ def test_seaborn_wrapper_creation(): def test_seaborn_wrapper_data_conversion(mocker): """Test that wrapped functions convert data correctly.""" + sns = pytest.importorskip("seaborn") result = Result( base_dataframe=pl.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]}), info={}, @@ -136,30 +137,32 @@ def test_seaborn_wrapper_data_conversion(mocker): ) data = result.data.to_pandas() pyprobe_seaborn_plot = plot.seaborn.lineplot(data=result, x="x", y="y") - seaborn_lineplot = _sns.lineplot(data=data, x="x", y="y") + seaborn_lineplot = sns.lineplot(data=data, x="x", y="y") assert pyprobe_seaborn_plot == seaborn_lineplot def test_seaborn_wrapper_function_call(): """Test that wrapped functions produce same output.""" + sns = pytest.importorskip("seaborn") wrapper = plot._create_seaborn_wrapper() - assert wrapper.set_theme() == _sns.set_theme() + assert wrapper.set_theme() == sns.set_theme() colors1 = wrapper.color_palette() - colors2 = _sns.color_palette() + colors2 = sns.color_palette() assert colors1 == colors2 # Test with specific parameters palette1 = wrapper.color_palette("husl", 8) - palette2 = _sns.color_palette("husl", 8) + palette2 = sns.color_palette("husl", 8) assert palette1 == palette2 def test_seaborn_wrapper_function_properties(): """Test that wrapped functions maintain original properties.""" + sns = pytest.importorskip("seaborn") wrapper = plot._create_seaborn_wrapper() - original_func = _sns.lineplot + original_func = sns.lineplot wrapped_func = wrapper.lineplot assert wrapped_func.__name__ == original_func.__name__ @@ -168,8 +171,9 @@ def test_seaborn_wrapper_function_properties(): def test_seaborn_wrapper_complete_coverage(): """Test that all public seaborn attributes are wrapped.""" + sns = pytest.importorskip("seaborn") wrapper = plot._create_seaborn_wrapper() - sns_attrs = {attr for attr in dir(_sns) if not attr.startswith("_")} + sns_attrs = {attr for attr in dir(sns) if not attr.startswith("_")} wrapper_attrs = {attr for attr in dir(wrapper) if not attr.startswith("_")} assert sns_attrs == wrapper_attrs diff --git a/uv.lock b/uv.lock index 8968c944..f6ecc063 100644 --- a/uv.lock +++ b/uv.lock @@ -1858,100 +1858,88 @@ dependencies = [ ] [package.optional-dependencies] -hvplot = [ - { name = "hvplot" }, -] -seaborn = [ - { name = "seaborn" }, -] - -[package.dev-dependencies] -docs = [ - { name = "autodoc-pydantic" }, - { name = "nbsphinx" }, - { name = "pydata-sphinx-theme" }, - { name = "sphinx" }, - { name = "sphinx-design" }, - { name = "sphinx-tabs" }, - { name = "sphinxcontrib-bibtex" }, - { name = "xlsxwriter" }, -] -external-integration = [ - { name = "pybamm" }, -] -format = [ +dev = [ { name = "black" }, { name = "flake8" }, { name = "flake8-docstrings" }, { name = "isort" }, { name = "mypy" }, - { name = "pre-commit" }, - { name = "python-semantic-release" }, -] -test = [ { name = "nbmake" }, + { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "pytest-mock" }, { name = "pytest-mypy" }, + { name = "python-semantic-release" }, { name = "types-deprecated" }, { name = "types-pyyaml" }, { name = "types-toml" }, + { name = "xlsxwriter" }, +] +docs = [ + { name = "autodoc-pydantic" }, + { name = "nbsphinx" }, + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, + { name = "sphinx-design" }, + { name = "sphinx-tabs" }, + { name = "sphinxcontrib-bibtex" }, +] +hvplot = [ + { name = "hvplot" }, +] +pybamm = [ + { name = "pybamm" }, +] +seaborn = [ + { name = "seaborn" }, ] [package.metadata] requires-dist = [ + { name = "autodoc-pydantic", marker = "extra == 'docs'", specifier = ">=2.2.0" }, + { name = "black", marker = "extra == 'dev'", specifier = ">=24.10.0" }, { name = "deprecated", specifier = ">=1.2.15" }, { name = "distinctipy", specifier = ">=1.3.4" }, { name = "fastexcel", specifier = ">=0.12.0" }, + { name = "flake8", marker = "extra == 'dev'", specifier = ">=7.1.1" }, + { name = "flake8-docstrings", marker = "extra == 'dev'", specifier = ">=1.7.0" }, { name = "hvplot", marker = "extra == 'hvplot'", specifier = ">=0.11.2" }, { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "isort", marker = "extra == 'dev'", specifier = ">=5.13.2" }, { name = "matplotlib", specifier = ">=3.10.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.14.1" }, + { name = "nbmake", marker = "extra == 'dev'", specifier = ">=1.5.5" }, + { name = "nbsphinx", marker = "extra == 'docs'", specifier = ">=0.9.6" }, { name = "numpy", specifier = ">=2.2.1" }, { name = "ordered-set", specifier = ">=4.1.0" }, { name = "plotly", specifier = ">=5.24.1" }, { name = "polars", specifier = ">=1.18.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0.1" }, + { name = "pybamm", marker = "extra == 'pybamm'", specifier = ">=24.1" }, { name = "pydantic", specifier = ">=2.10.4" }, + { name = "pydata-sphinx-theme", marker = "extra == 'docs'", specifier = ">=0.16.1" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.4" }, + { name = "pytest-benchmark", marker = "extra == 'dev'", specifier = ">=5.1.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0" }, + { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.14.0" }, + { name = "pytest-mypy", marker = "extra == 'dev'", specifier = ">=0.10.3" }, + { name = "python-semantic-release", marker = "extra == 'dev'", specifier = ">=9.15.2" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "ray", specifier = ">=2.40.0" }, { name = "scikit-learn", specifier = ">=1.6.0" }, { name = "seaborn", marker = "extra == 'seaborn'", specifier = ">=0.13.2" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=8.1.3" }, + { name = "sphinx-design", marker = "extra == 'docs'", specifier = ">=0.6.1" }, + { name = "sphinx-tabs", marker = "extra == 'docs'", specifier = ">=3.4.7" }, + { name = "sphinxcontrib-bibtex", marker = "extra == 'docs'", specifier = ">=2.6.3" }, { name = "streamlit", specifier = ">=1.41.1" }, { name = "sympy", specifier = ">=1.13.3" }, -] - -[package.metadata.requires-dev] -docs = [ - { name = "autodoc-pydantic", specifier = ">=2.2.0" }, - { name = "nbsphinx", specifier = ">=0.9.6" }, - { name = "pydata-sphinx-theme", specifier = ">=0.16.1" }, - { name = "sphinx", specifier = ">=8.1.3" }, - { name = "sphinx-design", specifier = ">=0.6.1" }, - { name = "sphinx-tabs", specifier = ">=3.4.7" }, - { name = "sphinxcontrib-bibtex", specifier = ">=2.6.3" }, - { name = "xlsxwriter", specifier = ">=3.2.0" }, -] -external-integration = [{ name = "pybamm", specifier = ">=24.1" }] -format = [ - { name = "black", specifier = ">=24.10.0" }, - { name = "flake8", specifier = ">=7.1.1" }, - { name = "flake8-docstrings", specifier = ">=1.7.0" }, - { name = "isort", specifier = ">=5.13.2" }, - { name = "mypy", specifier = ">=1.14.1" }, - { name = "pre-commit", specifier = ">=4.0.1" }, - { name = "python-semantic-release", specifier = ">=9.15.2" }, -] -test = [ - { name = "nbmake", specifier = ">=1.5.5" }, - { name = "pytest", specifier = ">=8.3.4" }, - { name = "pytest-benchmark", specifier = ">=5.1.0" }, - { name = "pytest-cov", specifier = ">=6.0.0" }, - { name = "pytest-mock", specifier = ">=3.14.0" }, - { name = "pytest-mypy", specifier = ">=0.10.3" }, - { name = "types-deprecated", specifier = ">=1.2.15.20241117" }, - { name = "types-pyyaml", specifier = ">=6.0.12.20241230" }, - { name = "types-toml", specifier = ">=0.10.8.20240310" }, + { name = "types-deprecated", marker = "extra == 'dev'", specifier = ">=1.2.15.20241117" }, + { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.12.20241230" }, + { name = "types-toml", marker = "extra == 'dev'", specifier = ">=0.10.8.20240310" }, + { name = "xlsxwriter", marker = "extra == 'dev'", specifier = ">=3.2.0" }, ] [[package]]