Skip to content

Commit

Permalink
[Python] Defer import of h5py until time of use
Browse files Browse the repository at this point in the history
This avoids potential conflicts between the versions of libhdf5 linked
to the Cantera library and h5py, which could occur when a C++ main
application made use of the Python ExtensibleRate class.
  • Loading branch information
speth committed Jan 12, 2023
1 parent 0aca5f7 commit f550faa
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 40 deletions.
14 changes: 5 additions & 9 deletions .github/workflows/main.yml
Expand Up @@ -53,7 +53,7 @@ jobs:
run: |
python3 -m pip install ruamel.yaml scons==3.1.2 numpy cython pandas pytest \
pytest-github-actions-annotate-failures
python3 -m pip install h5py || python3 -m pip install --no-binary=h5py h5py
python3 -m pip install h5py
- name: Build Cantera
run: |
python3 `which scons` build env_vars=all -j2 debug=n --debug=time \
Expand Down Expand Up @@ -94,8 +94,7 @@ jobs:
run: python3 -m pip install -U pip setuptools wheel
- name: Install Python dependencies
run: |
python3 -m pip install ruamel.yaml scons numpy cython pandas pytest pytest-github-actions-annotate-failures
python3 -m pip install --no-binary=h5py h5py
python3 -m pip install ruamel.yaml scons numpy cython pandas h5py pytest pytest-github-actions-annotate-failures
- name: Build Cantera
run: python3 `which scons` build env_vars=all
CXX=clang++-12 CC=clang-12 f90_interface=n extra_lib_dirs=/usr/lib/llvm/lib
Expand Down Expand Up @@ -194,9 +193,8 @@ jobs:
run: python3 -m pip install -U pip setuptools wheel
- name: Install Python dependencies
run: |
python3 -m pip install ruamel.yaml scons numpy cython pandas scipy pytest \
python3 -m pip install ruamel.yaml scons numpy cython pandas scipy pytest h5py \
pytest-github-actions-annotate-failures pytest-cov gcovr
python3 -m pip install --no-binary=h5py h5py
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v2
with:
Expand Down Expand Up @@ -366,8 +364,7 @@ jobs:
run: python3 -m pip install -U pip setuptools wheel
- name: Install Python dependencies
run: |
python3 -m pip install ruamel.yaml scons numpy cython pandas matplotlib scipy
python3 -m pip install --no-binary=h5py h5py
python3 -m pip install ruamel.yaml scons numpy cython pandas matplotlib scipy h5py
- name: Build Cantera
# compile with GCC 9.4.0 on ubuntu-20.04 as an alternative to the default
# (GCC 7.5.0 is both default and oldest supported version)
Expand Down Expand Up @@ -619,9 +616,8 @@ jobs:
run: python3 -m pip install -U pip setuptools wheel
- name: Install Python dependencies
run: |
python3 -m pip install ruamel.yaml scons numpy cython pandas pytest \
python3 -m pip install ruamel.yaml scons numpy cython pandas h5py pytest \
pytest-github-actions-annotate-failures
python3 -m pip install --no-binary=h5py h5py
- name: Setup Intel oneAPI environment
run: |
source /opt/intel/oneapi/setvars.sh
Expand Down
37 changes: 19 additions & 18 deletions interfaces/cython/cantera/composite.py
Expand Up @@ -7,17 +7,20 @@
from collections import OrderedDict
import csv as _csv

import pkg_resources

# avoid explicit dependence of cantera on h5py
try:
pkg_resources.get_distribution('h5py')
except pkg_resources.DistributionNotFound:
_h5py = ImportError('Method requires a working h5py installation.')
else:
import h5py as _h5py
def _import_h5py():
# avoid explicit dependence of cantera on h5py
import pkg_resources # local import to reduce overall import time
try:
pkg_resources.get_distribution('h5py')
except pkg_resources.DistributionNotFound:
raise ImportError('Method requires a working h5py installation.')
else:
import h5py
return h5py

# avoid explicit dependence of cantera on pandas
import pkg_resources

try:
pkg_resources.get_distribution('pandas')
except pkg_resources.DistributionNotFound:
Expand Down Expand Up @@ -1311,8 +1314,7 @@ def write_hdf(self, filename, *args, cols=None, group=None, subgroup=None,
requires a working installation of *h5py* (``h5py`` can be installed using
pip or conda).
"""
if isinstance(_h5py, ImportError):
raise _h5py
h5py = _import_h5py()

# collect data
data = self.collect_data(*args, cols=cols, **kwargs)
Expand All @@ -1322,7 +1324,7 @@ def write_hdf(self, filename, *args, cols=None, group=None, subgroup=None,
hdf_kwargs = {k: v for k, v in hdf_kwargs.items() if v is not None}

# save to container file
with _h5py.File(filename, mode) as hdf:
with h5py.File(filename, mode) as hdf:

# check existence of tagged item
if not group:
Expand Down Expand Up @@ -1395,10 +1397,9 @@ def read_hdf(self, filename, group=None, subgroup=None, force=False, normalize=T
The method imports data using `restore_data` and requires a working
installation of *h5py* (``h5py`` can be installed using pip or conda).
"""
if isinstance(_h5py, ImportError):
raise _h5py
h5py = _import_h5py()

with _h5py.File(filename, 'r') as hdf:
with h5py.File(filename, 'r') as hdf:

groups = list(hdf.keys())
if not len(groups):
Expand All @@ -1417,7 +1418,7 @@ def read_hdf(self, filename, group=None, subgroup=None, force=False, normalize=T
# identify subgroup
if subgroup is not None:
sub_names = [key for key, value in root.items()
if isinstance(value, _h5py.Group)]
if isinstance(value, h5py.Group)]
if not len(sub_names):
msg = "HDF group '{}' does not contain valid data"
raise IOError(msg.format(group))
Expand Down Expand Up @@ -1454,13 +1455,13 @@ def strip_ext(source):
self._meta = dict(dgroup.attrs.items())
for name, value in dgroup.items():
# support one level of recursion
if isinstance(value, _h5py.Group):
if isinstance(value, h5py.Group):
self._meta[name] = dict(value.attrs.items())

# load data
data = OrderedDict()
for name, value in dgroup.items():
if isinstance(value, _h5py.Group):
if isinstance(value, h5py.Group):
continue
elif value.dtype.type == np.bytes_:
data[name] = np.array(value).astype('U')
Expand Down
4 changes: 0 additions & 4 deletions test/kinetics/kineticsFromYaml.cpp
Expand Up @@ -518,10 +518,6 @@ TEST(Reaction, PythonExtensibleRate)
#ifndef CT_HAS_PYTHON
GTEST_SKIP();
#endif
#ifdef CT_USE_HDF5
// potential mismatch of HDF libraries between h5py and HighFive
GTEST_SKIP();
#endif
auto sol = newSolution("extensible-reactions.yaml");
auto R = sol->kinetics()->reaction(0);
EXPECT_EQ(R->type(), "square-rate");
Expand Down
21 changes: 14 additions & 7 deletions test/python/test_composite.py
Expand Up @@ -5,7 +5,14 @@
import pickle

import cantera as ct
from cantera.composite import _h5py, _pandas

try:
h5py = ct.composite._import_h5py()
have_h5py = True
except ImportError:
have_h5py = False

from cantera.composite import _pandas
from . import utilities


Expand Down Expand Up @@ -255,7 +262,7 @@ def test_append_no_norm_data(self):
self.assertEqual(states[0].P, gas.P)
self.assertArrayNear(states[0].Y, gas.Y)

@utilities.unittest.skipIf(isinstance(_h5py, ImportError), "h5py is not installed")
@utilities.unittest.skipIf(not have_h5py, "h5py is not installed")
def test_import_no_norm_data(self):
outfile = self.test_work_path / "solutionarray.h5"
# In Python >= 3.8, this can be replaced by the missing_ok argument
Expand Down Expand Up @@ -336,7 +343,7 @@ def test_to_pandas(self):
with self.assertRaisesRegex(NotImplementedError, 'not supported'):
states.to_pandas()

@utilities.unittest.skipIf(isinstance(_h5py, ImportError), "h5py is not installed")
@utilities.unittest.skipIf(not have_h5py, "h5py is not installed")
def test_write_hdf(self):
outfile = self.test_work_path / "solutionarray.h5"
# In Python >= 3.8, this can be replaced by the missing_ok argument
Expand Down Expand Up @@ -365,7 +372,7 @@ def test_write_hdf(self):
gas = ct.Solution('gri30.yaml', transport_model=None)
ct.SolutionArray(gas, 10).write_hdf(outfile)

with _h5py.File(outfile, 'a') as hdf:
with h5py.File(outfile, 'a') as hdf:
hdf.create_group('spam')

c = ct.SolutionArray(self.gas)
Expand All @@ -382,7 +389,7 @@ def test_write_hdf(self):
c.read_hdf(outfile, group='foo/bar/baz')
self.assertArrayNear(states.T, c.T)

@utilities.unittest.skipIf(isinstance(_h5py, ImportError), "h5py is not installed")
@utilities.unittest.skipIf(not have_h5py, "h5py is not installed")
def test_write_hdf_str_column(self):
outfile = self.test_work_path / "solutionarray.h5"
# In Python >= 3.8, this can be replaced by the missing_ok argument
Expand All @@ -396,7 +403,7 @@ def test_write_hdf_str_column(self):
b.read_hdf(outfile)
self.assertEqual(list(states.spam), list(b.spam))

@utilities.unittest.skipIf(isinstance(_h5py, ImportError), "h5py is not installed")
@utilities.unittest.skipIf(not have_h5py, "h5py is not installed")
def test_write_hdf_multidim_column(self):
outfile = self.test_work_path / "solutionarray.h5"
# In Python >= 3.8, this can be replaced by the missing_ok argument
Expand Down Expand Up @@ -571,7 +578,7 @@ def check(a, b):
b.restore_data(data)
check(a, b)

@utilities.unittest.skipIf(isinstance(_h5py, ImportError), "h5py is not installed")
@utilities.unittest.skipIf(not have_h5py, "h5py is not installed")
def test_import_no_norm_water(self):
outfile = self.test_work_path / "solutionarray.h5"
# In Python >= 3.8, this can be replaced by the missing_ok argument
Expand Down
2 changes: 0 additions & 2 deletions test/python/test_onedim.py
Expand Up @@ -4,8 +4,6 @@
from .utilities import allow_deprecated
import pytest

from cantera.composite import _h5py


class TestOnedim(utilities.CanteraTest):
def test_instantiate(self):
Expand Down

0 comments on commit f550faa

Please sign in to comment.