diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b126372768..7538458670 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,6 +77,7 @@ jobs: # - check-crosshair-nocover # - check-crosshair-niche - check-py38-oldestnumpy + - check-numpy-nightly fail-fast: false steps: - uses: actions/checkout@v3 diff --git a/AUTHORS.rst b/AUTHORS.rst index fe47bf7527..8b56decfee 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -96,6 +96,7 @@ their individual contributions. * `Jonty Wareing `_ (jonty@jonty.co.uk) * `Joshua Boone `_ (joshuaboone4190@gmail.com) * `jmhsi `_ +* `Justus Magin `_ * `jwg4 `_ * `Kai Chen `_ (kaichen120@gmail.com) * `Karthikeyan Singaravelan `_ (tir.karthi@gmail.com) diff --git a/hypothesis-python/RELEASE.rst b/hypothesis-python/RELEASE.rst new file mode 100644 index 0000000000..a09e90423f --- /dev/null +++ b/hypothesis-python/RELEASE.rst @@ -0,0 +1,4 @@ +RELEASE_TYPE: patch + +Explicitly cast :obj:`numpy.finfo.smallest_normal` to builtin `float` in +preparation for the :pypi:`numpy==2.0 ` release (:issue:`3950`) diff --git a/hypothesis-python/src/hypothesis/extra/array_api.py b/hypothesis-python/src/hypothesis/extra/array_api.py index 8c82f63114..85e5f3f8f9 100644 --- a/hypothesis-python/src/hypothesis/extra/array_api.py +++ b/hypothesis-python/src/hypothesis/extra/array_api.py @@ -282,7 +282,7 @@ def check_valid_minmax(prefix, val, info_obj): if allow_subnormal is not None: kw["allow_subnormal"] = allow_subnormal else: - subnormal = next_down(finfo.smallest_normal, width=finfo.bits) + subnormal = next_down(float(finfo.smallest_normal), width=finfo.bits) ftz = bool(xp.asarray(subnormal, dtype=dtype) == 0) if ftz: kw["allow_subnormal"] = False @@ -303,7 +303,7 @@ def check_valid_minmax(prefix, val, info_obj): # complex array, in case complex arrays have different FTZ behaviour # than arrays of the respective composite float. if allow_subnormal is None: - subnormal = next_down(finfo.smallest_normal, width=finfo.bits) + subnormal = next_down(float(finfo.smallest_normal), width=finfo.bits) x = xp.asarray(complex(subnormal, subnormal), dtype=dtype) builtin_x = complex(x) allow_subnormal = builtin_x.real != 0 and builtin_x.imag != 0 diff --git a/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py b/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py index 448f7e51ac..53bee1fe22 100644 --- a/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py +++ b/hypothesis-python/src/hypothesis/strategies/_internal/strategies.py @@ -908,9 +908,9 @@ def _collection_ish_functions(): np.diag, # bonus undocumented functions from tab-completion: np.asarray_chkfinite, - np.asfarray, np.asfortranarray, ] + return funcs diff --git a/hypothesis-python/tests/array_api/conftest.py b/hypothesis-python/tests/array_api/conftest.py index 630df48363..128f357fb9 100644 --- a/hypothesis-python/tests/array_api/conftest.py +++ b/hypothesis-python/tests/array_api/conftest.py @@ -35,7 +35,8 @@ f"HYPOTHESIS_TEST_ARRAY_API_VERSION='{test_version_option}' is not " f"'default' or a valid api_version {NOMINAL_VERSIONS}." ) -with pytest.warns(HypothesisWarning): +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=HypothesisWarning) mock_version = "draft" if test_version_option == "default" else test_version_option mock_xps = make_strategies_namespace(mock_xp, api_version=mock_version) api_version = None if test_version_option == "default" else test_version_option diff --git a/hypothesis-python/tests/array_api/test_arrays.py b/hypothesis-python/tests/array_api/test_arrays.py index f63b03b9f6..c9fe8e28ce 100644 --- a/hypothesis-python/tests/array_api/test_arrays.py +++ b/hypothesis-python/tests/array_api/test_arrays.py @@ -8,6 +8,8 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. +import sys + import pytest from hypothesis import given, settings, strategies as st @@ -296,6 +298,25 @@ def test_may_not_fill_unique_array_with_non_nan(xp, xps): check_can_generate_examples(strat) +@pytest.mark.skipif(sys.version_info[:2] <= (3, 8), reason="no complex") +def test_floating_point_array(): + import warnings + from hypothesis.extra.array_api import make_strategies_namespace + + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import numpy.array_api as nxp + except ModuleNotFoundError: + import numpy as nxp + xps = make_strategies_namespace(nxp) + dtypes = xps.floating_dtypes() | xps.complex_dtypes() + + strat = xps.arrays(dtype=dtypes, shape=10) + + check_can_generate_examples(strat) + + @pytest.mark.parametrize( "kwargs", [ diff --git a/hypothesis-python/tests/array_api/test_partial_adoptors.py b/hypothesis-python/tests/array_api/test_partial_adoptors.py index 6de5d9dbb8..422b109acf 100644 --- a/hypothesis-python/tests/array_api/test_partial_adoptors.py +++ b/hypothesis-python/tests/array_api/test_partial_adoptors.py @@ -11,6 +11,8 @@ from copy import copy from types import SimpleNamespace from typing import Tuple +import functools +import warnings import pytest @@ -31,17 +33,73 @@ MOCK_WARN_MSG = f"determine.*{mock_xp.__name__}.*Array API" -def make_mock_xp(*, exclude: Tuple[str, ...] = ()) -> SimpleNamespace: +class MockedArray: + def __init__(self, wrapped, *, exclude=()): + self.wrapped = wrapped + self.exclude = exclude + + def __getattr__(self, name): + if name in self.exclude: + raise AttributeError(f"removed on the mock: {name}") + + return object.__getattr__(self, name) + + +def wrap_array(func: callable, exclude: Tuple[str, ...] = ()) -> callable: + @functools.wraps(func) + def wrapped(*args, **kwargs): + result = func(*args, **kwargs) + + if isinstance(result, tuple): + return tuple(MockedArray(arr, exclude=exclude) for arr in result) + + return MockedArray(result, exclude=exclude) + + return wrapped + + +def make_mock_xp( + *, exclude: Tuple[str, ...] = (), exclude_methods: Tuple[str, ...] = () +) -> SimpleNamespace: xp = copy(mock_xp) assert isinstance(exclude, tuple) # sanity check + assert isinstance(exclude_methods, tuple) # sanity check for attr in exclude: delattr(xp, attr) + + array_returning_funcs = ( + "astype", + "broadcast_arrays", + "arange", + "asarray", + "empty", + "zeros", + "ones", + "reshape", + "isnan", + "isfinite", + "logical_or", + "sum", + "nonzero", + "sort", + "unique_values", + "any", + "all", + ) + + for name in array_returning_funcs: + func = getattr(xp, name, None) + if func is None: + # removed in the step before + continue + setattr(xp, name, wrap_array(func, exclude=exclude_methods)) + return xp def test_warning_on_noncompliant_xp(): """Using non-compliant array modules raises helpful warning""" - xp = make_mock_xp() + xp = make_mock_xp(exclude_methods=("__array_namespace__",)) with pytest.warns(HypothesisWarning, match=MOCK_WARN_MSG): make_strategies_namespace(xp, api_version="draft") @@ -62,7 +120,8 @@ def test_error_on_missing_attr(stratname, args, attr): dtypeless_xp = make_mock_xp(exclude=tuple(DTYPE_NAMES)) -with pytest.warns(HypothesisWarning): +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=HypothesisWarning) dtypeless_xps = make_strategies_namespace(dtypeless_xp, api_version="draft") diff --git a/hypothesis-python/tests/numpy/test_from_dtype.py b/hypothesis-python/tests/numpy/test_from_dtype.py index 8dbb9d21c6..4316f00577 100644 --- a/hypothesis-python/tests/numpy/test_from_dtype.py +++ b/hypothesis-python/tests/numpy/test_from_dtype.py @@ -21,6 +21,8 @@ from tests.common.debug import assert_no_examples, check_can_generate_examples, find_any +np_version = tuple(int(x) for x in np.__version__.split(".")[:2]) + STANDARD_TYPES = [ np.dtype(t) for t in ( @@ -126,7 +128,12 @@ def test_byte_string_dtypes_generate_unicode_strings(data): assert isinstance(result, bytes) -@pytest.mark.parametrize("dtype", ["U", "S", "a"]) +skipif_np2 = pytest.mark.skipif(np_version >= (2, 0), reason="removed in new version") + +@pytest.mark.parametrize( + "dtype", + ["U", "S", pytest.param("a", marks=skipif_np2)], +) def test_unsized_strings_length_gt_one(dtype): # See https://github.com/HypothesisWorks/hypothesis/issues/2229 find_any(nps.arrays(dtype=dtype, shape=1), lambda arr: len(arr[0]) >= 2) diff --git a/hypothesis-python/tests/numpy/test_gen_data.py b/hypothesis-python/tests/numpy/test_gen_data.py index f16ef8bd7f..c5d7ee4dd0 100644 --- a/hypothesis-python/tests/numpy/test_gen_data.py +++ b/hypothesis-python/tests/numpy/test_gen_data.py @@ -348,7 +348,11 @@ def test_may_not_fill_with_non_nan_when_unique_is_set_and_type_is_not_number(arr @pytest.mark.parametrize("fill", [False, True]) # Overflowing elements deprecated upstream in Numpy 1.24 :-) -@fails_with(InvalidArgument if np_version < (1, 24) else DeprecationWarning) +@fails_with( + InvalidArgument + if np_version < (1, 24) + else (DeprecationWarning if np_version < (2, 0) else OverflowError) +) @given(st.data()) def test_overflowing_integers_are_deprecated(fill, data): kw = {"elements": st.just(300)} diff --git a/hypothesis-python/tox.ini b/hypothesis-python/tox.ini index 2b8c5bf356..23c3bcbfe7 100644 --- a/hypothesis-python/tox.ini +++ b/hypothesis-python/tox.ini @@ -55,6 +55,19 @@ commands= bash -c "pip install --only-binary=:all: numpy==$(grep 'numpy>=' setup.py | grep -oE '[0-9.]+')" python -bb -X dev -m pytest tests/numpy/ -n auto +# This test job runs against the nightly version of `numpy` +[testenv:numpy-nightly] +deps= + -r../requirements/test.txt + pandas + black + click +allowlist_externals = + bash +commands= + bash -c "pip install --upgrade --pre --only-binary :all: -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy" + python -bb -X dev -m pytest tests/numpy/ tests/array_api/ tests/pandas/ tests/ghostwriter/ tests/conjecture/ tests/cover -n auto + # Note: when adding or removing tested Pandas versions, make sure to update the # docs in numpy.rst too. To see current download rates of each minor version: # https://pepy.tech/project/pandas?versions=1.1.*&versions=1.2.*&versions=1.3.*&versions=1.4.*&versions=1.5.*&versions=2.0.* diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py index 08cd84bab6..9a2db040b9 100644 --- a/tooling/src/hypothesistooling/__main__.py +++ b/tooling/src/hypothesistooling/__main__.py @@ -502,6 +502,7 @@ def standard_tox_task(name, py=ci_version): standard_tox_task(f"crosshair-{kind}") standard_tox_task("py38-oldestnumpy", py="3.8") +standard_tox_task("numpy-nightly", py="3.12") standard_tox_task("coverage") standard_tox_task("conjecture-coverage")