diff --git a/.all-contributorsrc b/.all-contributorsrc
index c78f369325..bbc0147dec 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -836,6 +836,16 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "lorenzofavaro",
+ "name": "Lorenzo",
+ "avatar_url": "https://avatars.githubusercontent.com/u/44714920?v=4",
+ "profile": "https://github.com/lorenzofavaro",
+ "contributions": [
+ "code",
+ "test"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b493145da..b1e093f452 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,7 @@
## Breaking changes
+- Integrated the `[pandas]` extra into the core PyBaMM package, deprecating the `pybamm[pandas]` optional dependency. Pandas is now a required dependency and will be installed upon installing PyBaMM ([#3892](https://github.com/pybamm-team/PyBaMM/pull/3892))
- Renamed "have_optional_dependency" to "import_optional_dependency" ([#3866](https://github.com/pybamm-team/PyBaMM/pull/3866))
- Integrated the `[latexify]` extra into the core PyBaMM package, deprecating the `pybamm[latexify]` set of optional dependencies. SymPy is now a required dependency and will be installed upon installing PyBaMM ([#3848](https://github.com/pybamm-team/PyBaMM/pull/3848))
- Renamed "testing" argument for plots to "show_plot" and flipped its meaning (show_plot=True is now the default and shows the plot) ([#3842](https://github.com/pybamm-team/PyBaMM/pull/3842))
diff --git a/all_contributors.md b/all_contributors.md
index 8cb18cdb35..efbbc254cf 100644
--- a/all_contributors.md
+++ b/all_contributors.md
@@ -104,6 +104,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
AKHIL SHARMA 📖 |
Harshvir Sandhu 💻 |
+
+ Lorenzo 💻 ⚠️ |
+
diff --git a/docs/source/user_guide/installation/index.rst b/docs/source/user_guide/installation/index.rst
index 1a773c65a5..00887bab6c 100644
--- a/docs/source/user_guide/installation/index.rst
+++ b/docs/source/user_guide/installation/index.rst
@@ -69,6 +69,7 @@ Package Minimum supp
`Anytree `__ 2.8.0
`SymPy `__ 1.9.3
`typing-extensions `__ 4.10.0
+`pandas `__ 1.5.0
=================================================================== ==========================
.. _install.optional_dependencies:
@@ -97,19 +98,6 @@ Dependency Minimum Version p
`matplotlib `__ 3.6.0 plot To plot various battery models, and analyzing battery performance.
=========================================================== ================== ================== ==================================================================
-.. _install.pandas_dependencies:
-
-Pandas dependencies
-^^^^^^^^^^^^^^^^^^^
-
-Installable with ``pip install "pybamm[pandas]"``
-
-=========================================================== ================== ================== ==================================================================
-Dependency Minimum Version pip extra Notes
-=========================================================== ================== ================== ==================================================================
-`pandas `__ 1.5.0 pandas For data manipulation and analysis.
-=========================================================== ================== ================== ==================================================================
-
.. _install.docs_dependencies:
Docs dependencies
diff --git a/pyproject.toml b/pyproject.toml
index 4e0b66bebf..d477419b91 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,6 +43,7 @@ dependencies = [
"anytree>=2.8.0",
"sympy>=1.12",
"typing-extensions>=4.10.0",
+ "pandas>=1.5.0"
]
[project.urls]
@@ -112,10 +113,8 @@ dev = [
"pytest>=6",
"pytest-xdist",
"nbmake",
-]
-# Reading CSV files
-pandas = [
- "pandas>=1.5.0",
+ # To access the metadata for python packages
+ "importlib-metadata; python_version < '3.10'",
]
# For the Jax solver. Note: these must be kept in sync with the versions defined in pybamm/util.py.
jax = [
@@ -130,7 +129,7 @@ odes = [
all = [
"autograd>=1.6.2",
"scikit-fem>=8.1.0",
- "pybamm[examples,plot,cite,bpx,tqdm,pandas]",
+ "pybamm[examples,plot,cite,bpx,tqdm]",
]
[project.scripts]
diff --git a/tests/__init__.py b/tests/__init__.py
index 919605998e..e057191c71 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -37,5 +37,8 @@
get_unit_2p1D_mesh_for_testing,
get_cylindrical_discretisation_for_testing,
get_base_model_with_battery_geometry,
+ get_required_distribution_deps,
+ get_optional_distribution_deps,
+ get_present_optional_import_deps,
)
from .testcase import TestCase
diff --git a/tests/shared.py b/tests/shared.py
index c863cac175..fbcb18f292 100644
--- a/tests/shared.py
+++ b/tests/shared.py
@@ -3,6 +3,13 @@
#
import pybamm
from scipy.sparse import eye
+import sys
+import re
+
+if sys.version_info < (3, 10):
+ import importlib_metadata
+else:
+ import importlib.metadata as importlib_metadata
class SpatialMethodForTesting(pybamm.SpatialMethod):
@@ -276,3 +283,35 @@ def get_base_model_with_battery_geometry(**kwargs):
model = pybamm.BaseModel()
model._geometry = pybamm.battery_geometry(**kwargs)
return model
+
+
+def get_required_distribution_deps(package_name):
+ pattern = re.compile(r"(?!.*extra\b)^([^<>=;\[]+)\b.*$")
+ if json_deps := importlib_metadata.metadata(package_name).json.get("requires_dist"):
+ return {m.group(1) for dep_name in json_deps if (m := pattern.match(dep_name))}
+ return set()
+
+
+def get_optional_distribution_deps(package_name):
+ pattern = re.compile(rf"(?!.*{package_name}\b|.*docs\b|.*dev\b)^([^<>=;\[]+)\b.*$")
+ if json_deps := importlib_metadata.metadata(package_name).json.get("requires_dist"):
+ return {
+ m.group(1)
+ for dep_name in json_deps
+ if (m := pattern.match(dep_name)) and "extra" in m.group(0)
+ }
+ return set()
+
+
+def get_present_optional_import_deps(package_name, optional_distribution_deps=None):
+ if optional_distribution_deps is None:
+ optional_distribution_deps = get_optional_distribution_deps(package_name)
+
+ present_optional_import_deps = set()
+ for (
+ import_pkg,
+ distribution_pkgs,
+ ) in importlib_metadata.packages_distributions().items():
+ if any(dep in optional_distribution_deps for dep in distribution_pkgs):
+ present_optional_import_deps.add(import_pkg)
+ return present_optional_import_deps
diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py
index 9be5f6a6e2..c880fd5604 100644
--- a/tests/unit/test_util.py
+++ b/tests/unit/test_util.py
@@ -1,6 +1,7 @@
#
# Tests the utility functions.
#
+import importlib
from tests import TestCase
import numpy as np
import os
@@ -10,9 +11,12 @@
import unittest
from unittest.mock import patch
from io import StringIO
-from tempfile import TemporaryDirectory
-anytree = sys.modules["anytree"]
+from tests import (
+ get_optional_distribution_deps,
+ get_required_distribution_deps,
+ get_present_optional_import_deps,
+)
class TestUtil(TestCase):
@@ -32,7 +36,6 @@ def test_rmse(self):
pybamm.rmse(np.ones(5), np.zeros(3))
def test_is_constant_and_can_evaluate(self):
- sys.modules["anytree"] = anytree
symbol = pybamm.PrimaryBroadcast(0, "negative electrode")
self.assertEqual(False, pybamm.is_constant_and_can_evaluate(symbol))
symbol = pybamm.StateVector(slice(0, 1))
@@ -100,27 +103,70 @@ def test_git_commit_info(self):
self.assertEqual(git_commit_info[:2], "v2")
def test_import_optional_dependency(self):
- with self.assertRaisesRegex(
- ModuleNotFoundError, "Optional dependency pybtex is not available."
- ):
- pybtex = sys.modules["pybtex"]
- sys.modules["pybtex"] = None
- pybamm.print_citations()
- with self.assertRaisesRegex(
- ModuleNotFoundError, "Optional dependency anytree is not available."
- ):
- with TemporaryDirectory() as dir_name:
- sys.modules["anytree"] = None
- test_stub = os.path.join(dir_name, "test_visualize")
- test_name = f"{test_stub}.png"
- c = pybamm.Variable("c", "negative electrode")
- d = pybamm.Variable("d", "negative electrode")
- sym = pybamm.div(c * pybamm.grad(c)) + (c / d + c - d) ** 5
- sym.visualise(test_name)
-
- sys.modules["pybtex"] = pybtex
- pybamm.util.import_optional_dependency("pybtex")
- pybamm.print_citations()
+ optional_distribution_deps = get_optional_distribution_deps("pybamm")
+ present_optional_import_deps = get_present_optional_import_deps(
+ "pybamm", optional_distribution_deps=optional_distribution_deps
+ )
+
+ # Save optional dependencies, then set to None
+ modules = {}
+ for import_pkg in present_optional_import_deps:
+ modules[import_pkg] = sys.modules.get(import_pkg)
+ sys.modules[import_pkg] = None
+
+ # Test import optional dependency
+ for import_pkg in present_optional_import_deps:
+ with self.assertRaisesRegex(
+ ModuleNotFoundError,
+ f"Optional dependency {import_pkg} is not available.",
+ ):
+ pybamm.util.import_optional_dependency(import_pkg)
+
+ # Restore optional dependencies
+ for import_pkg in present_optional_import_deps:
+ sys.modules[import_pkg] = modules[import_pkg]
+
+ def test_pybamm_import(self):
+ optional_distribution_deps = get_optional_distribution_deps("pybamm")
+ present_optional_import_deps = get_present_optional_import_deps(
+ "pybamm", optional_distribution_deps=optional_distribution_deps
+ )
+
+ # Save optional dependencies, then set to None
+ modules = {}
+ for import_pkg in present_optional_import_deps:
+ modules[import_pkg] = sys.modules.get(import_pkg)
+ sys.modules[import_pkg] = None
+
+ # Test pybamm is still importable
+ try:
+ importlib.reload(importlib.import_module("pybamm"))
+ except ModuleNotFoundError as error:
+ self.fail(
+ f"Import of 'pybamm' shouldn't require optional dependencies. Error: {error}"
+ )
+
+ # Restore optional dependencies
+ for import_pkg in present_optional_import_deps:
+ sys.modules[import_pkg] = modules[import_pkg]
+
+ def test_optional_dependencies(self):
+ optional_distribution_deps = get_optional_distribution_deps("pybamm")
+ required_distribution_deps = get_required_distribution_deps("pybamm")
+
+ # Get nested required dependencies
+ for distribution_dep in list(required_distribution_deps):
+ required_distribution_deps.update(
+ get_required_distribution_deps(distribution_dep)
+ )
+
+ # Check that optional dependencies are not present in the core PyBaMM installation
+ optional_present_deps = optional_distribution_deps & required_distribution_deps
+ self.assertFalse(
+ bool(optional_present_deps),
+ f"Optional dependencies installed: {optional_present_deps}.\n"
+ "Please ensure that optional dependencies are not present in the core PyBaMM installation, or list them as required.",
+ )
class TestSearch(TestCase):