import os import sys from inspect import cleandoc from itertools import chain from string import ascii_letters, digits from unittest import mock import numpy as np import pytest import shapely from shapely.decorators import multithreading_enabled, requires_geos @pytest.fixture def mocked_geos_version(): with mock.patch.object(shapely.lib, "geos_version", new=(3, 10, 1)): yield "3.10.1" @pytest.fixture def sphinx_doc_build(): os.environ["SPHINX_DOC_BUILD"] = "1" yield del os.environ["SPHINX_DOC_BUILD"] def test_version(): assert isinstance(shapely.__version__, str) def test_geos_version(): expected = "{}.{}.{}".format(*shapely.geos_version) actual = shapely.geos_version_string # strip any beta / dev qualifiers if any(c.isalpha() for c in actual): if actual[-1].isnumeric(): actual = actual.rstrip(digits) actual = actual.rstrip(ascii_letters) assert actual == expected def test_geos_capi_version(): expected = "{}.{}.{}-CAPI-{}.{}.{}".format( *(shapely.geos_version + shapely.geos_capi_version) ) # split into component parts and strip any beta / dev qualifiers ( actual_geos_version, actual_geos_api_version, ) = shapely.geos_capi_version_string.split("-CAPI-") if any(c.isalpha() for c in actual_geos_version): if actual_geos_version[-1].isnumeric(): actual_geos_version = actual_geos_version.rstrip(digits) actual_geos_version = actual_geos_version.rstrip(ascii_letters) actual_geos_version = actual_geos_version.rstrip(ascii_letters) assert f"{actual_geos_version}-CAPI-{actual_geos_api_version}" == expected def func(): """Docstring that will be mocked. A multiline. Some description. """ class SomeClass: def func(self): """Docstring that will be mocked. A multiline. Some description. """ def expected_docstring(**kwds): doc = """Docstring that will be mocked. {indent}A multiline. {indent}.. note:: 'func' requires at least GEOS {version}. {indent}Some description. {indent}""".format(**kwds) if sys.version_info[:2] >= (3, 13): # There are subtle differences between inspect.cleandoc() and # _PyCompile_CleanDoc(). Most significantly, the latter does not remove # leading or trailing blank lines. return cleandoc(doc) + "\n" return doc @pytest.mark.parametrize("version", ["3.10.0", "3.10.1", "3.9.2"]) def test_requires_geos_ok(version, mocked_geos_version): wrapped = requires_geos(version)(func) wrapped() assert wrapped is func @pytest.mark.parametrize("version", ["3.10.2", "3.11.0", "3.11.1"]) def test_requires_geos_not_ok(version, mocked_geos_version): wrapped = requires_geos(version)(func) with pytest.raises(shapely.errors.UnsupportedGEOSVersionError): wrapped() assert wrapped.__doc__ == expected_docstring(version=version, indent=" " * 4) @pytest.mark.parametrize("version", ["3.9.0", "3.10.0"]) def test_requires_geos_doc_build(version, mocked_geos_version, sphinx_doc_build): """The requires_geos decorator always adapts the docstring.""" wrapped = requires_geos(version)(func) assert wrapped.__doc__ == expected_docstring(version=version, indent=" " * 4) @pytest.mark.parametrize("version", ["3.9.0", "3.10.0"]) def test_requires_geos_method(version, mocked_geos_version, sphinx_doc_build): """The requires_geos decorator adjusts methods docstrings correctly""" wrapped = requires_geos(version)(SomeClass.func) assert wrapped.__doc__ == expected_docstring(version=version, indent=" " * 8) @multithreading_enabled def set_first_element(value, *args, **kwargs): for arg in chain(args, kwargs.values()): if hasattr(arg, "__setitem__"): arg[0] = value return arg def test_multithreading_enabled_raises_arg(): arr = np.empty((1,), dtype=object) # set_first_element cannot change the input array with pytest.raises(ValueError): set_first_element(42, arr) # afterwards, we can arr[0] = 42 assert arr[0] == 42 def test_multithreading_enabled_raises_kwarg(): arr = np.empty((1,), dtype=object) # set_first_element cannot change the input array with pytest.raises(ValueError): set_first_element(42, arr=arr) # writable flag goes to original state assert arr.flags.writeable def test_multithreading_enabled_preserves_flag(): arr = np.empty((1,), dtype=object) arr.flags.writeable = False # set_first_element cannot change the input array with pytest.raises(ValueError): set_first_element(42, arr) # writable flag goes to original state assert not arr.flags.writeable @pytest.mark.parametrize( "args,kwargs", [ ((np.empty((1,), dtype=float),), {}), # float-dtype ndarray is untouched ((), {"a": np.empty((1,), dtype=float)}), (([1],), {}), # non-ndarray is untouched ((), {"a": [1]}), ((), {"out": np.empty((1,), dtype=object)}), # ufunc kwarg 'out' is untouched ( (), {"where": np.empty((1,), dtype=object)}, ), # ufunc kwarg 'where' is untouched ], ) def test_multithreading_enabled_ok(args, kwargs): result = set_first_element(42, *args, **kwargs) assert result[0] == 42
Memory