diff --git a/.github/workflows/end_to_end.yml b/.github/workflows/end_to_end.yml index f9f539db..418e5e97 100644 --- a/.github/workflows/end_to_end.yml +++ b/.github/workflows/end_to_end.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/minimum.yml b/.github/workflows/minimum.yml index e56b5a5e..832e7f08 100644 --- a/.github/workflows/minimum.yml +++ b/.github/workflows/minimum.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/numerical.yml b/.github/workflows/numerical.yml index 5c86bcb5..52d0f881 100644 --- a/.github/workflows/numerical.yml +++ b/.github/workflows/numerical.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index 4d76f2da..8be604ab 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/tutorials.yml b/.github/workflows/tutorials.yml index 9673a618..a59f58a7 100644 --- a/.github/workflows/tutorials.yml +++ b/.github/workflows/tutorials.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v1 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index b3c73a50..34c35314 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 diff --git a/copulas/__init__.py b/copulas/__init__.py index ac91762b..a1f2b7d5 100644 --- a/copulas/__init__.py +++ b/copulas/__init__.py @@ -11,11 +11,11 @@ import sys import warnings from copy import deepcopy +from importlib.metadata import entry_points from operator import attrgetter import numpy as np import pandas as pd -from pkg_resources import iter_entry_points EPSILON = np.finfo(np.float32).eps @@ -311,7 +311,13 @@ def _get_addon_target(addon_path_name): def _find_addons(): """Find and load all copulas add-ons.""" group = 'copulas_modules' - for entry_point in iter_entry_points(group=group): + try: + eps = entry_points(group=group) + except TypeError: + # Load-time selection requires Python >= 3.10 or importlib_metadata >= 3.6 + eps = entry_points().get(group, []) + + for entry_point in eps: try: addon = entry_point.load() except Exception: # pylint: disable=broad-exception-caught diff --git a/pyproject.toml b/pyproject.toml index ac4adb2b..a9f5429b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,22 +12,25 @@ classifiers = [ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Scientific/Engineering :: Artificial Intelligence', ] keywords = [ 'copulas' ] dynamic = ["version"] license = { text = 'BSL-1.1' } -requires-python = '>=3.8,<3.12' +requires-python = '>=3.8,<3.13' readme = 'README.md' dependencies = [ "numpy>=1.20.0,<2;python_version<'3.10'", - "numpy>=1.23.3,<2;python_version>='3.10'", + "numpy>=1.23.3,<2;python_version>='3.10' and python_version<'3.12'", + "numpy>=1.26.0,<2;python_version>='3.12'", "pandas>=1.1.3;python_version<'3.10'", "pandas>=1.3.4;python_version>='3.10' and python_version<'3.11'", "pandas>=1.5.0;python_version>='3.11'", 'plotly>=5.10.0,<6', "scipy>=1.5.4,<2;python_version<'3.10'", - "scipy>=1.9.2,<2;python_version>='3.10'", + "scipy>=1.9.2,<2;python_version>='3.10' and python_version<'3.12'", + "scipy>=1.12.0,<2;python_version>='3.12'", ] [project.urls] @@ -47,14 +50,15 @@ version = {attr = 'copulas.__version__'} [project.optional-dependencies] tutorials = [ 'markupsafe<=2.0.1', - 'scikit-learn>=0.24,<1.2', + "scikit-learn>=0.24,<1.2;python_version<'3.10'", + "scikit-learn>=0.24,<1.5;python_version>='3.10'", 'jupyter>=1.0.0,<2', ] test = [ 'copulas[tutorials]', 'pytest>=6.2.5,<7', 'pytest-cov>=2.6.0,<3', - 'pytest-rerunfailures>=9.0.0,<10', + 'pytest-rerunfailures>=10.3,<15', 'rundoc>=0.4.3,<0.5', 'tomli>=2.0.0,<3', ] @@ -65,7 +69,7 @@ dev = [ 'pip>=9.0.1', 'build>=1.0.0,<2', 'bump-my-version>=0.18.3,<1', - 'watchdog>=0.8.3,<0.11', + 'watchdog>=1.0.1,<5', # docs 'm2r>=0.2.0,<0.3', @@ -164,7 +168,6 @@ namespaces = false '*.png', '*.gif' ] -'tests' = ['*'] [tool.setuptools.exclude-package-data] '*' = [ diff --git a/tests/unit/bivariate/test_base.py b/tests/unit/bivariate/test_base.py index e8513897..0f42fe61 100644 --- a/tests/unit/bivariate/test_base.py +++ b/tests/unit/bivariate/test_base.py @@ -80,7 +80,7 @@ def test_save(self, json_mock, open_mock): instance.save('test.json') # Check - assert open_mock.called_once_with('test.json', 'w') + open_mock.assert_called_once_with('test.json', 'w') assert json_mock.called compare_nested_dicts(json_mock.call_args[0][0], expected_content) @@ -99,10 +99,10 @@ def test_load_from_file(self, json_mock, open_mock): instance = Bivariate.load('somefile.json') # Check - assert open_mock.called_once_with('test.json', 'r') - instance.copula_type == CopulaTypes.FRANK - instance.tau == -0.33333333333333337 - instance.theta == -3.305771759329249 + open_mock.assert_called_once_with('somefile.json') + assert instance.copula_type == CopulaTypes.FRANK + assert instance.tau == -0.33333333333333337 + assert instance.theta == -3.305771759329249 @mock.patch('copulas.bivariate.clayton.Clayton.partial_derivative') def test_partial_derivative_scalar(self, derivative_mock): diff --git a/tests/unit/multivariate/test_gaussian.py b/tests/unit/multivariate/test_gaussian.py index 2e7060b5..7c31e774 100644 --- a/tests/unit/multivariate/test_gaussian.py +++ b/tests/unit/multivariate/test_gaussian.py @@ -392,11 +392,15 @@ def test_sample(self, normal_mock): # Check assert result.equals(expected_result) - assert normal_mock.called_once_with( - np.zeros(instance.correlation.shape[0]), - instance.correlation, - 5 + np.testing.assert_array_equal( + normal_mock.call_args[0][0], + np.zeros(instance.correlation.shape[0]) ) + np.testing.assert_array_equal( + normal_mock.call_args[0][1], + instance.correlation + ) + assert normal_mock.call_args[1] == {'size': 5} def test_sample_random_state(self): """When random_state is set the samples are the same.""" diff --git a/tests/unit/test___init__.py b/tests/unit/test___init__.py index 4d88f5f0..0d21a303 100644 --- a/tests/unit/test___init__.py +++ b/tests/unit/test___init__.py @@ -435,7 +435,7 @@ def mock_copulas(): sys.modules['copulas'] = copulas_module -@patch.object(copulas, 'iter_entry_points') +@patch.object(copulas, 'entry_points') def test__find_addons_module(entry_points_mock, mock_copulas): """Test loading an add-on.""" # Setup @@ -452,7 +452,7 @@ def test__find_addons_module(entry_points_mock, mock_copulas): assert mock_copulas.submodule.entry_name == 'entry_point' -@patch.object(copulas, 'iter_entry_points') +@patch.object(copulas, 'entry_points') def test__find_addons_object(entry_points_mock, mock_copulas): """Test loading an add-on.""" # Setup @@ -470,7 +470,7 @@ def test__find_addons_object(entry_points_mock, mock_copulas): @patch('warnings.warn') -@patch('copulas.iter_entry_points') +@patch('copulas.entry_points') def test__find_addons_bad_addon(entry_points_mock, warning_mock): """Test failing to load an add-on generates a warning.""" # Setup @@ -493,7 +493,7 @@ def entry_point_error(): @patch('warnings.warn') -@patch('copulas.iter_entry_points') +@patch('copulas.entry_points') def test__find_addons_wrong_base(entry_points_mock, warning_mock): """Test incorrect add-on name generates a warning.""" # Setup @@ -514,7 +514,7 @@ def test__find_addons_wrong_base(entry_points_mock, warning_mock): @patch('warnings.warn') -@patch('copulas.iter_entry_points') +@patch('copulas.entry_points') def test__find_addons_missing_submodule(entry_points_mock, warning_mock): """Test incorrect add-on name generates a warning.""" # Setup @@ -535,7 +535,7 @@ def test__find_addons_missing_submodule(entry_points_mock, warning_mock): @patch('warnings.warn') -@patch('copulas.iter_entry_points') +@patch('copulas.entry_points') def test__find_addons_module_and_object(entry_points_mock, warning_mock): """Test incorrect add-on name generates a warning.""" # Setup @@ -556,7 +556,7 @@ def test__find_addons_module_and_object(entry_points_mock, warning_mock): @patch('warnings.warn') -@patch.object(copulas, 'iter_entry_points') +@patch.object(copulas, 'entry_points') def test__find_addons_missing_object(entry_points_mock, warning_mock, mock_copulas): """Test incorrect add-on name generates a warning.""" # Setup diff --git a/tox.ini b/tox.ini index baaff459..633abe92 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py39-lint, py3{8,9,10,11}-{readme,unit,end_to_end,numerical,minimum,tutorials} +envlist = py39-lint, py3{8,9,10,11,12}-{readme,unit,end_to_end,numerical,minimum,tutorials} [testenv] skipsdist = false diff --git a/tutorials/04_Syntehtic_Data_for_Machine_Learning.ipynb b/tutorials/04_Syntehtic_Data_for_Machine_Learning.ipynb index 92b7f652..2ccbb879 100644 --- a/tutorials/04_Syntehtic_Data_for_Machine_Learning.ipynb +++ b/tutorials/04_Syntehtic_Data_for_Machine_Learning.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -26,10 +26,10 @@ "\n", "warnings.filterwarnings('ignore')\n", "\n", - "from sklearn.datasets import load_boston\n", + "from sklearn.datasets import load_diabetes\n", "from sklearn.model_selection import train_test_split\n", "\n", - "X, y = load_boston(return_X_y=True)\n", + "X, y = load_diabetes(return_X_y=True)\n", "X_train, X_test, y_train, y_test = train_test_split(X, y)" ] }, @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -82,9 +82,427 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
ElasticNet()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
ElasticNet()