diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 14e5a1186..7fc8b24ff 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -12,10 +12,10 @@ jobs: name: build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - name: Build the sdist and the wheel @@ -41,7 +41,7 @@ jobs: echo "Checking import and version number (on release)" venv-bdist/bin/python -c "import pymc_extras as pmx; assert pmx.__version__ == '${{ github.ref_name }}'[1:] if '${{ github.ref_type }}' == 'tag' else pmx.__version__; print(pmx.__version__)" cd .. - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: artifact path: dist/* @@ -58,8 +58,8 @@ jobs: # write id-token is necessary for trusted publishing (OIDC) id-token: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: artifact path: dist - - uses: pypa/gh-action-pypi-publish@release/v1 + - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 503f5c344..e17b6882a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,8 +43,8 @@ jobs: run: shell: bash -leo pipefail {0} steps: - - uses: actions/checkout@v4 - - uses: mamba-org/setup-micromamba@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: mamba-org/setup-micromamba@d7c9bd84e824b79d2af72a2d4196c7f4300d3476 # v3.0.0 with: environment-file: conda-envs/environment-test.yml create-args: >- @@ -62,7 +62,7 @@ jobs: run: | python -m pytest --color=yes -vv --cov=pymc_extras --cov-append --cov-report=xml --cov-report term --durations=50 $TEST_SUBSET - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 with: env_vars: TEST_SUBSET name: ${{ matrix.os }} @@ -89,11 +89,13 @@ jobs: run: shell: cmd /C call {0} steps: - - uses: actions/checkout@v4 - - uses: mamba-org/setup-micromamba@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: mamba-org/setup-micromamba@d7c9bd84e824b79d2af72a2d4196c7f4300d3476 # v3.0.0 with: environment-file: conda-envs/environment-test.yml - micromamba-version: "latest" + # Pinned: micromamba 2.6.1-0 ships a Windows binary with a missing DLL + # dependency that breaks GitHub-hosted runners (STATUS_DLL_NOT_FOUND). + micromamba-version: "2.6.0-0" create-args: >- python=${{matrix.python-version}} environment-name: pymc-extras-test @@ -110,7 +112,7 @@ jobs: run: >- python -m pytest --color=yes -vv --cov=pymc_extras --cov-append --cov-report=xml --cov-report term --durations=50 %TEST_SUBSET% - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 with: env_vars: TEST_SUBSET name: ${{ matrix.os }} diff --git a/conda-envs/environment-test.yml b/conda-envs/environment-test.yml index 6f16875b6..9089ebb57 100644 --- a/conda-envs/environment-test.yml +++ b/conda-envs/environment-test.yml @@ -4,7 +4,7 @@ channels: - nodefaults dependencies: - scikit-learn - - better-optimize>=0.3.2 + - better-optimize>=0.4.1 - dask<2025.1.1 - xhistogram - statsmodels @@ -13,9 +13,9 @@ dependencies: - pytest-cov - pydantic>=2.0.0 - h5netcdf + - pymc>=6.0,<7.0 - pip - pip: - jax - blackjax - - pymc @ git+https://github.com/pymc-devs/pymc.git - - preliz @ git+https://github.com/arviz-devs/preliz.git + - preliz>=0.25 diff --git a/pymc_extras/inference/pathfinder/pathfinder.py b/pymc_extras/inference/pathfinder/pathfinder.py index ad235d9b2..dc6abd8da 100644 --- a/pymc_extras/inference/pathfinder/pathfinder.py +++ b/pymc_extras/inference/pathfinder/pathfinder.py @@ -24,7 +24,7 @@ from collections.abc import Callable, Iterator from dataclasses import asdict, dataclass, field, replace from enum import Enum, auto -from typing import Any, Literal, Self, TypeAlias +from typing import Any, Literal, Self import numpy as np import pymc as pm @@ -79,7 +79,7 @@ REGULARISATION_TERM = 1e-8 DEFAULT_LINKER = "cvm_nogc" -SinglePathfinderFn: TypeAlias = Callable[[int], "PathfinderResult"] +type SinglePathfinderFn = Callable[[int], "PathfinderResult"] def get_jaxified_logp_of_ravel_inputs(model: Model, jacobian: bool = True) -> Callable: diff --git a/pymc_extras/linearmodel.py b/pymc_extras/linearmodel.py index 639cd1749..5eb7f09b7 100644 --- a/pymc_extras/linearmodel.py +++ b/pymc_extras/linearmodel.py @@ -2,7 +2,16 @@ import pandas as pd import pymc as pm -from sklearn.base import BaseEstimator +# If scikit-learn is available, inherit from BaseEstimator for sklearn-pipeline interop +# (Pipeline, TransformedTargetRegressor, get_params). Without it, fall back to a stub +# so LinearModel still works as a standalone Bayesian model. +try: + from sklearn.base import BaseEstimator +except ImportError: + + class BaseEstimator: + pass + from pymc_extras.model_builder import ModelBuilder diff --git a/pymc_extras/prior.py b/pymc_extras/prior.py index 6f2092367..66af1366e 100644 --- a/pymc_extras/prior.py +++ b/pymc_extras/prior.py @@ -90,7 +90,7 @@ def custom_transform(x): from functools import partial from inspect import signature from numbers import Number -from typing import TYPE_CHECKING, Any, Protocol, TypeAlias, runtime_checkable +from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable import numpy as np import pymc as pm @@ -111,7 +111,7 @@ def custom_transform(x): from pytensor.tensor import TensorLike from pytensor.xtensor.type import XTensorVariable - XTensorLike: TypeAlias = TensorLike | DataArray + type XTensorLike = TensorLike | DataArray class UnsupportedShapeError(Exception): diff --git a/pyproject.toml b/pyproject.toml index bed4dc906..433b3e87e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,9 +12,9 @@ classifiers = [ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "License :: OSI Approved :: Apache Software License", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering", @@ -34,11 +34,11 @@ keywords = [ license = {file = "LICENSE"} dynamic = ["version"] # specify the version in the __init__.py file dependencies = [ - "pymc@git+https://github.com/pymc-devs/pymc.git", - "scikit-learn", - "better-optimize>=0.3.1", + "pymc>=6.0,<7.0", + "arviz>=1.1", + "better-optimize>=0.4.1,<1.0", "pydantic>=2.0.0", - "preliz>=0.20.0", + "preliz>=0.25.0", ] [project.optional-dependencies] @@ -131,7 +131,7 @@ exclude_lines = [ [tool.ruff] line-length = 100 -target-version = "py311" +target-version = "py312" [tool.ruff.format] docstring-code-format = true diff --git a/tests/statespace/core/test_statespace.py b/tests/statespace/core/test_statespace.py index 406aaf9cb..11e259cb3 100644 --- a/tests/statespace/core/test_statespace.py +++ b/tests/statespace/core/test_statespace.py @@ -36,7 +36,7 @@ floatX = pytensor.config.floatX nile = load_nile_test_data() ALL_SAMPLE_OUTPUTS = MATRIX_NAMES + FILTER_OUTPUT_NAMES + SMOOTHER_OUTPUT_NAMES -mock_pymc_sample = pytest.fixture(scope="session")(mock_sample_setup_and_teardown) +mock_pymc_sample = pytest.fixture(scope="module")(mock_sample_setup_and_teardown) def make_statespace_mod(k_endog, k_states, k_posdef, filter_type, verbose=False, data_info=None): @@ -393,7 +393,7 @@ def pymc_mod_time_varying(ss_mod_time_varying, rng): return m -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata(pymc_mod, rng, mock_pymc_sample): with pymc_mod: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -403,7 +403,7 @@ def idata(pymc_mod, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_exog(exog_pymc_mod, rng, mock_pymc_sample): with exog_pymc_mod: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -412,7 +412,7 @@ def idata_exog(exog_pymc_mod, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_exog_mv(exog_pymc_mod_mv, rng, mock_pymc_sample): with exog_pymc_mod_mv: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -421,7 +421,7 @@ def idata_exog_mv(exog_pymc_mod_mv, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_no_exog(pymc_mod_no_exog, rng, mock_pymc_sample): with pymc_mod_no_exog: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -430,7 +430,7 @@ def idata_no_exog(pymc_mod_no_exog, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_no_exog_mv(pymc_mod_no_exog_mv, rng, mock_pymc_sample): with pymc_mod_no_exog_mv: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -439,7 +439,7 @@ def idata_no_exog_mv(pymc_mod_no_exog_mv, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_no_exog_mv_dt(pymc_mod_no_exog_mv_dt, rng, mock_pymc_sample): with pymc_mod_no_exog_mv_dt: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -448,7 +448,7 @@ def idata_no_exog_mv_dt(pymc_mod_no_exog_mv_dt, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_no_exog_dt(pymc_mod_no_exog_dt, rng, mock_pymc_sample): with pymc_mod_no_exog_dt: idata = pm.sample(draws=10, tune=0, chains=1, random_seed=rng) @@ -457,7 +457,7 @@ def idata_no_exog_dt(pymc_mod_no_exog_dt, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_time_varying(pymc_mod_time_varying, rng, mock_pymc_sample): """Inference data for time-varying model.""" with pymc_mod_time_varying: diff --git a/tests/statespace/core/test_statespace_JAX.py b/tests/statespace/core/test_statespace_JAX.py index 1bdc5364c..78be2011a 100644 --- a/tests/statespace/core/test_statespace_JAX.py +++ b/tests/statespace/core/test_statespace_JAX.py @@ -31,7 +31,7 @@ nile = load_nile_test_data() ALL_SAMPLE_OUTPUTS = MATRIX_NAMES + FILTER_OUTPUT_NAMES + SMOOTHER_OUTPUT_NAMES -mock_pymc_sample = pytest.fixture(scope="session")(mock_sample_setup_and_teardown) +mock_pymc_sample = pytest.fixture(scope="module")(mock_sample_setup_and_teardown) @pytest.fixture(scope="session") @@ -68,7 +68,7 @@ def exog_pymc_mod(exog_ss_mod, rng): return m -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata(pymc_mod, rng, mock_pymc_sample): with warnings.catch_warnings(): warnings.simplefilter("ignore") @@ -91,7 +91,7 @@ def idata(pymc_mod, rng, mock_pymc_sample): return idata -@pytest.fixture(scope="session") +@pytest.fixture(scope="module") def idata_exog(exog_pymc_mod, rng, mock_pymc_sample): with warnings.catch_warnings(): warnings.simplefilter("ignore")