diff --git a/.github/workflows/test_python.yml b/.github/workflows/test_python.yml
index 9a2b10d3..a91fb553 100644
--- a/.github/workflows/test_python.yml
+++ b/.github/workflows/test_python.yml
@@ -1,26 +1,30 @@
-name: Test Python
+name: Test Python
on: [push, pull_request]
jobs:
- test_linux:
- runs-on: ubuntu-latest
+ test:
+ runs-on: ${{ matrix.runner }}
+ strategy:
+ fail-fast: false
+ matrix:
+ runner:
+ - ubuntu-latest
+ - macos-latest
+ python-version:
+ - "3.10"
+ - "3.x"
steps:
- - uses: actions/checkout@v2
+
+ - uses: actions/checkout@v6
with:
submodules: recursive
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v6
with:
- python-version: "3.x"
-
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip
+ python-version: ${{ matrix.python-version }}
- name: Install package for testing
- run: pip install ".[test]"
+ run: python -m pip install ".[test]"
- name: Run tests
- run: >
- export MPP_DIRECTORY=$(pwd) &&
- export MPP_DATA_DIRECTORY=$MPP_DIRECTORY/data &&
- pytest
+ run: pytest
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index e7ecda1b..0113e745 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -8,76 +8,62 @@ jobs:
test_linux:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v6
with:
submodules: recursive
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v6
with:
python-version: "3.x"
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip
-
- name: Install package for testing
- run: pip install ".[test]"
+ run: python -m pip install ".[test]"
- name: Run tests
- run: >
- export MPP_DIRECTORY=$(pwd) &&
- export MPP_DATA_DIRECTORY=$MPP_DIRECTORY/data &&
- pytest
+ run: pytest
test_mac:
runs-on: macos-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v6
with:
submodules: recursive
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v6
with:
python-version: "3.x"
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip
-
- name: Install package for testing
- run: pip install ".[test]"
+ run: python -m pip install ".[test]"
- name: Run tests
- run: >
- export MPP_DIRECTORY=$(pwd) &&
- export MPP_DATA_DIRECTORY=$MPP_DIRECTORY/data &&
- pytest
+ run: pytest
linux_wheels:
strategy:
matrix:
- python-version:
- - cp36-cp36m
- - cp37-cp37m
- - cp38-cp38
- - cp39-cp39
+ python-version:
+ - cp310-cp310
+ - cp311-cp311
+ - cp312-cp312
+ - cp313-cp313
+ - cp314-cp314
needs: test_linux
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v6
with:
submodules: recursive
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v6
with:
- python-version: 3.9
-
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip
+ python-version: 3.14
- name: Build manylinux Python wheels
uses: RalfG/python-wheels-manylinux-build@v0.3.4-manylinux2010_x86_64
with:
- python-versions: "${{ matrix.python-version }}"
+ python-versions: ${{ matrix.python-version }}
- uses: actions/upload-artifact@v2
with:
@@ -88,27 +74,28 @@ jobs:
mac_wheels:
strategy:
matrix:
- python-version:
- - 3.6
- - 3.7
- - 3.8
- - 3.9
+ python-version:
+ - "3.10"
+ - "3.11"
+ - "3.12"
+ - "3.13"
+ - "3.14"
needs: test_mac
runs-on: macos-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v6
with:
submodules: recursive
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v6
with:
- python-version: "${{ matrix.python-version }}"
+ python-version: ${{ matrix.python-version }}
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip
+ - name: Install build
+ run: python -m pip install build
- - name: Build wheels
- run: pip wheel . -w dist/
+ - name: Build wheel
+ run: python -m build --whell
- uses: actions/upload-artifact@v2
with:
@@ -120,19 +107,19 @@ jobs:
needs: test_linux
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v6
with:
submodules: recursive
- - uses: actions/setup-python@v2
+ - uses: actions/setup-python@v6
with:
- python-version: 3.9
+ python-version: "3.x"
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip scikit-build
+ - name: Install build
+ run: python -m pip install build
- name: Build sdist
- run: python setup.py sdist
+ run: python -m build --sdist
- uses: actions/upload-artifact@v2
with:
@@ -159,12 +146,12 @@ jobs:
name: sdist
path: dist
- - name: Upgrade pip
- run: pip install -U setuptools wheel pip
+ - uses: actions/setup-python@v6
+ with:
+ python-version: "3.x"
- name: Install twine
- run: pip install twine
+ run: python -m pip install twine
- name: Upload wheels
run: twine upload -u __token__ -p "${{ secrets.TESTPYPI_TOKEN }}" --repository testpypi dist/*
-
diff --git a/.gitignore b/.gitignore
index 7e372b97..30f732ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,8 +15,9 @@ docs/html/
.vscode/
# Python
+dist/
+**/__pycache__/
*.pyc
-_skbuild/
*.whl
# OS specifics
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e7c9bc32..d4327cc7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,7 +47,7 @@ if (BUILD_FORTRAN_WRAPPER)
endif()
#######################################################################
-# Python bindings built with pip + scikit-build #
+# Python bindings built with scikit-build-core + pybind11 #
#######################################################################
if (SKBUILD)
diff --git a/interface/python/.gitignore b/interface/python/.gitignore
index c6facb83..3eb8b6e0 100644
--- a/interface/python/.gitignore
+++ b/interface/python/.gitignore
@@ -1,6 +1,7 @@
###Build###
build/
dist/
+**/__pycache__/
mutationpp.egg-info/
*.so
diff --git a/interface/python/CMakeLists.txt b/interface/python/CMakeLists.txt
index 595b53ac..803aa71c 100644
--- a/interface/python/CMakeLists.txt
+++ b/interface/python/CMakeLists.txt
@@ -1,23 +1,18 @@
-if (CMAKE_VERSION VERSION_LESS 3.18)
- set(PY_DEV_MODULE Development)
-else()
- set(PY_DEV_MODULE Development.Module)
-endif()
-
-find_package(Python COMPONENTS Interpreter ${PY_DEV_MODULE} REQUIRED)
-
-execute_process(
- COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir
- OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE pybind11_ROOT)
+find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(_mutationpp
src/mutationpp_python.cpp
+ src/pyGlobalOptions.cpp
src/pyMixtureOptions.cpp
src/pyMixture.cpp
)
target_link_libraries(_mutationpp PRIVATE mutation++)
-install(TARGETS _mutationpp DESTINATION .)
+install(DIRECTORY mutationpp DESTINATION .)
+
+install(TARGETS _mutationpp DESTINATION mutationpp)
+
+install(DIRECTORY "${CMAKE_SOURCE_DIR}/data" DESTINATION mutationpp)
diff --git a/interface/python/DESCRIPTION.md b/interface/python/DESCRIPTION.md
new file mode 100644
index 00000000..33249638
--- /dev/null
+++ b/interface/python/DESCRIPTION.md
@@ -0,0 +1,3 @@
+
+
+*Mutation++* is an open-source library providing thermodynamic, transport, chemistry, and energy transfer properties associated with subsonic to hypersonic flows.
diff --git a/interface/python/mutationpp/__init__.py b/interface/python/mutationpp/__init__.py
index 2db0c2d5..63130b12 100644
--- a/interface/python/mutationpp/__init__.py
+++ b/interface/python/mutationpp/__init__.py
@@ -1 +1,3 @@
+"""Mutation++ Python bindings"""
+
from ._mutationpp import *
diff --git a/interface/python/src/mutationpp_python.cpp b/interface/python/src/mutationpp_python.cpp
index 05fba317..b1f76fc0 100644
--- a/interface/python/src/mutationpp_python.cpp
+++ b/interface/python/src/mutationpp_python.cpp
@@ -8,11 +8,13 @@
namespace py = pybind11;
+void py_export_GlobalOptions(py::module &);
void py_export_MixtureOptions(py::module &);
void py_export_Mixture(py::module &);
PYBIND11_MODULE(_mutationpp, m) {
m.doc() = "Mutation++ Python bindings";
+ py_export_GlobalOptions(m);
py_export_MixtureOptions(m);
py_export_Mixture(m);
}
diff --git a/interface/python/src/pyGlobalOptions.cpp b/interface/python/src/pyGlobalOptions.cpp
new file mode 100644
index 00000000..dfba9f14
--- /dev/null
+++ b/interface/python/src/pyGlobalOptions.cpp
@@ -0,0 +1,55 @@
+#include
+#include
+
+namespace py = pybind11;
+
+/**
+ * Python wrapper definition for the GlobalOptions class.
+ */
+
+namespace {
+
+ inline void setDefaultDataDirectory(const std::string& datadir) {
+ if (std::getenv("MPP_DATA_DIRECTORY") == nullptr) {
+ Mutation::GlobalOptions::dataDirectory(datadir);
+ }
+ };
+
+}
+
+void py_export_GlobalOptions(py::module &m) {
+
+ const py::object resources = py::module_::import("importlib.resources");
+ const py::object pkgname = m.attr("__spec__").attr("parent");
+ const py::object rootdir = resources.attr("files")(pkgname);
+ const py::object datadir = rootdir / py::str("data");
+ const std::string data_directory = py::str(datadir).cast();
+
+ setDefaultDataDirectory(data_directory);
+
+ /**
+ * Overloaded member functions wrappers
+ */
+
+ /**
+ * Python class definition
+ */
+ py::class_(m, "GlobalOptions")
+ .def_static("dataDirectory", [](const std::string& datadir) {
+ Mutation::GlobalOptions::dataDirectory(datadir);
+ })
+ .def_static("dataDirectory", []() {
+ return Mutation::GlobalOptions::dataDirectory();
+ })
+ .def_static("workingDirectory", [](const std::string& workdir) {
+ Mutation::GlobalOptions::workingDirectory(workdir);
+ })
+ .def_static("workingDirectory", []() {
+ return Mutation::GlobalOptions::workingDirectory();
+ })
+ .def_static("reset", [data_directory]() {
+ Mutation::GlobalOptions::reset();
+ setDefaultDataDirectory(data_directory);
+ })
+ ;
+}
diff --git a/pyproject.toml b/pyproject.toml
index 5df5edcd..9f0141e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,49 @@
+[project]
+name = "mutationpp"
+dynamic = ["version"]
+description = "MUlticomponent Thermodynamic And Transport properties for IONized gases in C++"
+readme = "interface/python/DESCRIPTION.md"
+license = "LGPL-3.0-only"
+license-files = ["COPYING", "COPYING.LESSER"]
+authors = [
+ {name = "James B. Scoggins", email = "james.scoggins@vki.ac.be"},
+]
+maintainers = [
+ {name = "Ruben Di Battista", email = "rubendibattista@gmail.com"},
+]
+classifiers = [
+ "Development Status :: 6 - Mature",
+ "Intended Audience :: Science/Research",
+ "Operating System :: MacOS",
+ "Operating System :: POSIX :: Linux",
+ "Programming Language :: C++",
+ "Programming Language :: Python :: 3",
+ "Topic :: Scientific/Engineering :: Chemistry",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+]
+requires-python = ">=3.10"
+dependencies = ["numpy"]
+[project.urls]
+Homepage = "https://github.com/mutationpp/Mutationpp"
+Source = "https://github.com/mutationpp/Mutationpp.git"
+Issues = "https://github.com/mutationpp/Mutationpp/issues"
+Discussions = "https://github.com/mutationpp/Mutationpp/discussions"
+Downloads = "https://github.com/mutationpp/Mutationpp/releases"
+[project.optional-dependencies]
+test = ["pytest"]
+
[build-system]
-requires = ["setuptools", "wheel", "pybind11", "scikit-build", "cmake", "ninja"]
-build-backend = "setuptools.build_meta"
+requires = ["scikit-build-core>=0.11", "pybind11"]
+build-backend = "scikit_build_core.build"
+
+[tool.scikit-build]
+experimental = true
+sdist.cmake = false
+sdist.exclude = [".*"]
+[tool.scikit-build.metadata.version]
+provider = "scikit_build_core.metadata.regex"
+regex = '^project\(mutation\+\+\s*\n^\s*VERSION\s*(?P\d+\.\d+\.\d+)\s*$'
+input = "CMakeLists.txt"
[tool.pytest.ini_options]
testpaths = [
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 3197aef9..00000000
--- a/setup.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import re
-import sys
-
-from pathlib import Path
-
-try:
- from skbuild import setup
-except ImportError:
- print(
- "Please update pip, you need pip 10 or greater,\n"
- " or you need to install the PEP 518 requirements in pyproject.toml yourself",
- file=sys.stderr,
- )
- raise
-
-ROOT_CMAKELISTS = "./CMakeLists.txt"
-
-DESCRIPTION = """An open-source library providing thermodynamic, transport,\
-chemistry, and energy transfer properties associated with subsonic to\
-hypersonic flows."""
-
-
-def get_version_from_cmake(root_cmakelists):
- """A helper function to parse the root CMakeLists.txt and retrieve the
- version from there in order to have a single point holding the version
- info, easier to update"""
-
- pattern = re.compile(r" *VERSION *(\d+\.\d+\.\d+)")
-
- root_file = Path(root_cmakelists)
-
- with open(root_file, "r") as f:
- for line in f:
- match = pattern.search(line)
- if match is not None:
- return match.group(1)
-
- raise RuntimeError(
- "Couldn't parse CMakeLists.txt file to find a version statement"
- )
-
-
-setup(
- name="mutationpp",
- version=get_version_from_cmake(ROOT_CMAKELISTS),
- description=DESCRIPTION,
- long_description=DESCRIPTION,
- author="James B. Scoggins",
- license="LGPL3",
- package_dir={"": "interface/python"},
- packages=["mutationpp"],
- extras_require={
- "test": ["numpy", "pytest"],
- },
- cmake_install_dir="interface/python/mutationpp",
-)
diff --git a/src/general/GlobalOptions.h b/src/general/GlobalOptions.h
index 02f5fcc3..fb9f80be 100644
--- a/src/general/GlobalOptions.h
+++ b/src/general/GlobalOptions.h
@@ -22,6 +22,9 @@
#ifndef GLOBAL_OPTIONS_H
#define GLOBAL_OPTIONS_H
+#include
+#include
+
namespace Mutation {
/// Singleton class providing access to global options.
diff --git a/tests/python/test_options.py b/tests/python/test_options.py
new file mode 100644
index 00000000..466f8648
--- /dev/null
+++ b/tests/python/test_options.py
@@ -0,0 +1,68 @@
+import importlib.resources
+import os
+import pytest
+import unittest.mock as mock
+import mutationpp as mpp
+
+PACKAGE_DATADIR = os.fspath(importlib.resources.files(mpp) / "data")
+INITIAL_DATADIR = os.environ.get("MPP_DATA_DIRECTORY", PACKAGE_DATADIR)
+
+
+def test_options_datadir_initial():
+ datadir = mpp.GlobalOptions.dataDirectory()
+ assert datadir == INITIAL_DATADIR
+
+
+def test_options_workdir_initial():
+ workdir = mpp.GlobalOptions.workingDirectory()
+ assert workdir == ""
+
+
+@pytest.fixture
+def options():
+ datadir = mpp.GlobalOptions.dataDirectory()
+ workdir = mpp.GlobalOptions.workingDirectory()
+ yield {"datadir": datadir, "workdir": workdir}
+ mpp.GlobalOptions.dataDirectory(datadir)
+ mpp.GlobalOptions.workingDirectory(workdir)
+
+
+@pytest.fixture
+def datadir(options):
+ return options["datadir"]
+
+
+@pytest.fixture
+def workdir(options):
+ return options["workdir"]
+
+
+def test_options_datadir_getset(datadir):
+ new_datadir = os.path.join(datadir, "subdir")
+ mpp.GlobalOptions.dataDirectory(new_datadir)
+ set_datadir = mpp.GlobalOptions.dataDirectory()
+ assert set_datadir == new_datadir
+
+
+def test_options_workdir_getset(workdir):
+ new_workdir = os.path.join(workdir, "subdir")
+ mpp.GlobalOptions.workingDirectory(new_workdir)
+ set_workdir = mpp.GlobalOptions.workingDirectory()
+ assert set_workdir == new_workdir
+
+
+def test_options_reset(datadir):
+ new_datadir = os.path.join(datadir, "subdir")
+ environment = {"MPP_DATA_DIRECTORY": new_datadir}
+ with mock.patch.dict(os.environ, environment):
+ mpp.GlobalOptions.reset()
+ set_datadir = mpp.GlobalOptions.dataDirectory()
+ assert set_datadir == new_datadir
+ set_workdir = mpp.GlobalOptions.workingDirectory()
+ assert set_workdir == ""
+ with mock.patch.dict(os.environ, clear=True):
+ mpp.GlobalOptions.reset()
+ set_datadir = mpp.GlobalOptions.dataDirectory()
+ assert set_datadir == PACKAGE_DATADIR
+ set_workdir = mpp.GlobalOptions.workingDirectory()
+ assert set_workdir == ""