diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 12a29f91..9cd6c283 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.12' - name: Install dependencies run: | pip install . diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 243d4e76..7ba9023b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.9', '3.11'] + python-version: ['3.9', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a3a73dfd..7172d5cc 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,7 +12,7 @@ sphinx: build: os: "ubuntu-22.04" tools: - python: "3.11" + python: "3.12" python: install: diff --git a/docs/conf.py b/docs/conf.py index 3b98ca8c..6fc3527a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -158,7 +158,7 @@ 'xarray': ('https://docs.xarray.dev/en/stable/', None), 'numpy': ('https://numpy.org/doc/stable/', None), 'scipy': ('https://docs.scipy.org/doc/scipy/', None), - 'pytest': ('https://docs.pytest.org/en/7.1.x/', None), + 'pytest': ('https://docs.pytest.org/en/8.0.x/', None), 'openpyxl': ('https://openpyxl.readthedocs.io/en/stable', None) } diff --git a/docs/installation.rst b/docs/installation.rst index 154199d5..3f509113 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -41,7 +41,7 @@ PySD requires **Python 3.9** or above. PySD builds on the core Python data analytics stack, and the following third party libraries: -* Numpy < 1.24 +* Numpy >= 1.23 * Scipy * Pandas (with Excel support: `pip install pandas[excel]`) * Parsimonious @@ -65,7 +65,7 @@ In order to plot model outputs as shown in :doc:`Getting started <../getting_sta * Matplotlib -To export data to netCDF (*.nc*) files: +To export data to netCDF (*.nc*) files or to serialize external objects: * netCDF4 diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 8cfab142..3f97d8e4 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -1,5 +1,34 @@ What's New ========== +v3.13.3 (2024/02/02) +-------------------- +New Features +~~~~~~~~~~~~ + +Breaking changes +~~~~~~~~~~~~~~~~ + +Deprecations +~~~~~~~~~~~~ + +Bug fixes +~~~~~~~~~ + +Documentation +~~~~~~~~~~~~~ +- Improve documentation for :py:mod:`netCDF4` dependency. (`@enekomartinmartinez `_) + +Performance +~~~~~~~~~~~ + +Internal Changes +~~~~~~~~~~~~~~~~ +- Support for Python 3.12. (`@enekomartinmartinez `_) +- Support for :py:mod:`numpy` >= 1.24. (`@enekomartinmartinez `_) +- Correct some warnings management in the tests. (`@enekomartinmartinez `_) +- Set :py:mod:`numpy` requirements to >= 1.23 to follow `NEP29 `_. (`@enekomartinmartinez `_) +- Custom error messages when :py:mod:`netCDF4` is missing (:issue:`435`). (`@enekomartinmartinez `_) + v3.13.2 (2024/01/09) -------------------- New Features diff --git a/pysd/_version.py b/pysd/_version.py index d8c1804a..49979b06 100644 --- a/pysd/_version.py +++ b/pysd/_version.py @@ -1 +1 @@ -__version__ = "3.13.2" +__version__ = "3.13.3" diff --git a/pysd/builders/python/python_expressions_builder.py b/pysd/builders/python/python_expressions_builder.py index bd11bfc7..5c4f1fe4 100644 --- a/pysd/builders/python/python_expressions_builder.py +++ b/pysd/builders/python/python_expressions_builder.py @@ -460,10 +460,9 @@ def build_not_implemented(self, arguments: dict) -> BuildAST: """ final_subscripts = self.reorder(arguments) warnings.warn( - "\n\nTrying to translate '" - + self.function.upper().replace("_", " ") + "Trying to translate '" + self.function.upper().replace("_", " ") + "' which it is not implemented on PySD. The translated " - + "model will crash... " + + "model will crash..." ) self.section.imports.add("functions", "not_implemented_function") diff --git a/pysd/py_backend/allocation.py b/pysd/py_backend/allocation.py index 81272543..699e263d 100644 --- a/pysd/py_backend/allocation.py +++ b/pysd/py_backend/allocation.py @@ -106,7 +106,11 @@ def get_functions(cls, q0, pp, kind): interval = interval.union(i) # Full allocation function -> function to solve - def full_allocation(x): return np.sum([func(x) for func in functions]) + def full_allocation(x): + if isinstance(x, np.ndarray): + # Fix to solve issues in the newest numpy versions + x = x.squeeze()[()] + return np.sum([func(x) for func in functions]) def_intervals = [] for subinterval in interval: diff --git a/pysd/py_backend/model.py b/pysd/py_backend/model.py index 3f69a189..2db13ad7 100644 --- a/pysd/py_backend/model.py +++ b/pysd/py_backend/model.py @@ -516,6 +516,11 @@ def initialize_external_data(self, externals=None): -------- :func:`pysd.py_backend.model.Macro.serialize_externals` + Note + ---- + To load externals from a netCDF file you need to have installed + the optional dependency `netCDF4`. + """ if not externals: @@ -534,7 +539,11 @@ def initialize_external_data(self, externals=None): if not externals.is_file(): raise FileNotFoundError(f"Invalid file path ({str(externals)})") - ds = xr.open_dataset(externals) + try: + ds = xr.open_dataset(externals) + except ValueError: # pragma: no cover + raise ModuleNotFoundError("No module named 'netCDF4'") + for ext in self._external_elements: if ext.py_name in ds.data_vars.keys(): @@ -607,6 +616,11 @@ def serialize_externals(self, export_path="externals.nc", -------- :func:`pysd.py_backend.model.Macro.initialize_external_data` + Note + ---- + To run this function you need to have installed the optional + dependency `netCDF4`. + """ data = {} metadata = {} @@ -679,7 +693,10 @@ def serialize_externals(self, export_path="externals.nc", for key, values in metadata.items(): ds[key].attrs = values - ds.to_netcdf(export_path) + try: + ds.to_netcdf(export_path) + except KeyError: # pragma: no cover + raise ModuleNotFoundError("No module named 'netCDF4'") def __include_for_serialization(self, ext, py_name_clean, data, metadata, lookup_dims, data_dims): @@ -1125,12 +1142,6 @@ def _set_components(self, params, new): new_function = self._constant_component(value, dims) self._dependencies[func_name] = {} - # this won't handle other statefuls... - if '_integ_' + func_name in dir(self.components): - warnings.warn("Replacing the equation of stock " - "'{}' with params...".format(key), - stacklevel=2) - # copy attributes from the original object to proper working # of internal functions new_function.__name__ = func_name diff --git a/pysd/py_backend/output.py b/pysd/py_backend/output.py index 473636dd..08aa9543 100644 --- a/pysd/py_backend/output.py +++ b/pysd/py_backend/output.py @@ -386,7 +386,7 @@ def make_flat_df(df, return_addresses, flatten=False): if is_dataarray and values[0].size == 1: # some elements are returned as 0-d arrays, convert # them to float - values = [float(x) for x in values] + values = [x.squeeze().values[()] for x in values] is_dataarray = False if flatten and is_dataarray: diff --git a/requirements.txt b/requirements.txt index 2d303804..2e14f4ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy<1.24 +numpy>=1.23 pandas[excel] parsimonious xarray>=2023.9 diff --git a/setup.py b/setup.py index 8fc393e2..6f08d560 100755 --- a/setup.py +++ b/setup.py @@ -25,6 +25,7 @@ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ], install_requires=open('requirements.txt').read().strip().split('\n'), package_data={ diff --git a/tests/conftest.py b/tests/conftest.py index 0cbe8b5b..26542a13 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -53,5 +53,8 @@ def ignore_warns(): # warnings to be ignored in the integration tests return [ "numpy.ndarray size changed, may indicate binary incompatibility.", - "Creating an ndarray from ragged nested sequences.*" + "Creating an ndarray from ragged nested sequences.*", + "datetime.datetime.* is deprecated and scheduled for removal in a " + "future version. Use timezone-aware objects to represent datetimes " + "in UTC.*", ] diff --git a/tests/pytest.ini b/tests/pytest.ini index 9805090c..6ddeef57 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -2,4 +2,6 @@ python_files = pytest_*/**/*.py pytest_*/*.py filterwarnings = error - ignore:numpy.ndarray size changed, may indicate binary incompatibility.:RuntimeWarning + always:numpy.ndarray size changed, may indicate binary incompatibility.:RuntimeWarning + always::DeprecationWarning + always::PendingDeprecationWarning \ No newline at end of file diff --git a/tests/pytest_builders/pytest_python.py b/tests/pytest_builders/pytest_python.py index 3fb6dcfa..67890d5a 100644 --- a/tests/pytest_builders/pytest_python.py +++ b/tests/pytest_builders/pytest_python.py @@ -1,6 +1,7 @@ -import pytest from pathlib import Path +import pytest + from pysd.builders.python.namespace import NamespaceManager from pysd.builders.python.subscripts import SubscriptManager from pysd.builders.python.python_model_builder import\ @@ -147,13 +148,13 @@ def test_referencebuilder_subscripts_warning(self, component, component.section.subscripts.mapping = { dim: [] for dim in subscripts} component.section.namespace.namespace = namespace - warning_message =\ + warn_message =\ f"The reference to '{origin_name}' in variable 'My Var' has "\ r"duplicated subscript ranges\. If mapping is used in one "\ r"of them, please, rewrite reference subscripts to avoid "\ r"duplicates\. Otherwise, the final model may crash\.\.\."\ - with pytest.warns(UserWarning, match=warning_message): + with pytest.warns(UserWarning, match=warn_message): ReferenceBuilder(reference_str, component) @pytest.mark.parametrize( diff --git a/tests/pytest_integration/pytest_integration_vensim_pathway.py b/tests/pytest_integration/pytest_integration_vensim_pathway.py index de24aac3..7395f239 100644 --- a/tests/pytest_integration/pytest_integration_vensim_pathway.py +++ b/tests/pytest_integration/pytest_integration_vensim_pathway.py @@ -564,6 +564,7 @@ "file": "test_subscripted_round.mdl", "warns": [ "Variable '.*' is defined with different .*types: '.*'", + "invalid value encountered in cast" ] }, "subscripted_smooth": { diff --git a/tests/pytest_pysd/pytest_errors.py b/tests/pytest_pysd/pytest_errors.py index 58f4d79a..3ffe489a 100644 --- a/tests/pytest_pysd/pytest_errors.py +++ b/tests/pytest_pysd/pytest_errors.py @@ -1,6 +1,8 @@ -import pytest +import re import shutil +import pytest + from pysd import read_vensim, read_xmile, load @@ -92,13 +94,21 @@ def test_loading_error(loader, model_path, raise_type, error_message): ] ) def test_not_implemented_and_incomplete(model_path): - with pytest.warns(UserWarning) as ws: + with pytest.warns() as record: model = read_vensim(model_path) - assert "'incomplete var' has no equation specified"\ - in str(ws[0].message) - assert "Trying to translate 'MY FUNC' which it is not implemented"\ - " on PySD. The translated model will crash..."\ - in str(ws[1].message) + + warn_message = "'incomplete var' has no equation specified" + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" + + warn_message = "Trying to translate 'MY FUNC' which it is "\ + "not implemented on PySD. The translated model will crash..." + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" with pytest.warns(RuntimeWarning, match="Call to undefined function, calling dependencies " diff --git a/tests/pytest_pysd/pytest_functions.py b/tests/pytest_pysd/pytest_functions.py index 72059e7e..a89367c9 100644 --- a/tests/pytest_pysd/pytest_functions.py +++ b/tests/pytest_pysd/pytest_functions.py @@ -574,7 +574,7 @@ def test_get_time_value_errors(self, measure, relativeto, lambda: 0, relativeto, np.random.randint(-100, 100), measure) def test_vector_select(self): - warning_message =\ + warn_message =\ r"Vensim's help says that numerical_action=5 computes the "\ r"product of selection_array \^ expression_array\. But, in fact,"\ r" Vensim is computing the product of expression_array \^ "\ @@ -584,7 +584,7 @@ def test_vector_select(self): array = xr.DataArray([3, 10, 2], {'dim': ["A", "B", "C"]}) sarray = xr.DataArray([1, 0, 2], {'dim': ["A", "B", "C"]}) - with pytest.warns(UserWarning, match=warning_message): + with pytest.warns(UserWarning, match=warn_message): assert vector_select(sarray, array, ["dim"], np.nan, 5, 1)\ == 12 diff --git a/tests/pytest_pysd/pytest_output.py b/tests/pytest_pysd/pytest_output.py index 53994236..c17d4200 100644 --- a/tests/pytest_pysd/pytest_output.py +++ b/tests/pytest_pysd/pytest_output.py @@ -116,7 +116,7 @@ class EmptyHandler(OutputHandlerInterface): @pytest.mark.parametrize("model_path", [test_model_look]) def test_invalid_output_file(self, model): - error_message = "expected str, bytes or os.PathLike object, not int" + error_message = ".* str.* os.PathLike object.*" with pytest.raises(TypeError, match=error_message): model.run(output_file=1234) diff --git a/tests/pytest_pysd/pytest_pysd.py b/tests/pytest_pysd/pytest_pysd.py index cb5b85fa..e38cbc09 100644 --- a/tests/pytest_pysd/pytest_pysd.py +++ b/tests/pytest_pysd/pytest_pysd.py @@ -1,3 +1,4 @@ +import re from pathlib import Path import pytest @@ -140,13 +141,13 @@ def test_run_return_timestamps(self, model): # assert one timestamp is not returned because is not multiple of # the time step - warning_message =\ + warn_message =\ "The returning time stamp '%s' seems to not be a multiple "\ "of the time step. This value will not be saved in the output. "\ "Please, modify the returning timestamps or the integration "\ "time step to avoid this." # assert that return_timestamps works with float error - with pytest.warns(UserWarning, match=warning_message % 0.55): + with pytest.warns(UserWarning, match=warn_message % 0.55): stocks = model.run( time_step=0.1, return_timestamps=[0.3, 0.1, 0.55, 0.9]) assert 0.1 in stocks.index @@ -155,7 +156,7 @@ def test_run_return_timestamps(self, model): assert 0.55 not in stocks.index with pytest.warns(UserWarning, - match=warning_message % "(0.15|0.55|0.95)"): + match=warn_message % "(0.15|0.55|0.95)"): stocks = model.run( time_step=0.1, return_timestamps=[0.3, 0.15, 0.55, 0.95]) assert 0.15 not in stocks.index @@ -348,13 +349,19 @@ def test_set_component_with_real_name(self, model): @pytest.mark.parametrize("model_path", [test_model]) def test_set_components_warnings(self, model): """Addresses https://github.com/SDXorg/pysd/issues/80""" - warn_message = r"Replacing the equation of stock "\ - r"'Teacup Temperature' with params\.\.\." - with pytest.warns(UserWarning, match=warn_message): + warn_message = r"Replacing the value of Stateful variable with "\ + r"an expression\. To set initial conditions use "\ + r"`set_initial_condition` instead\.\.\." + with pytest.warns() as record: model.set_components( {"Teacup Temperature": 20, "Characteristic Time": 15} ) # set stock value using params + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" + @pytest.mark.parametrize("model_path", [test_model]) def test_set_components_with_function(self, model): def test_func(): @@ -967,12 +974,17 @@ def test_set_initial_value_subscripted_value_with_numpy_error(self, model): @pytest.mark.parametrize("model_path", [test_model]) def test_replace_stateful(self, model): - warn_message = "Replacing the value of Stateful variable with "\ - "an expression. To set initial conditions use "\ - "`set_initial_condition` instead..." - with pytest.warns(UserWarning, match=warn_message): + warn_message = r"Replacing the value of Stateful variable with "\ + r"an expression\. To set initial conditions use "\ + r"`set_initial_condition` instead\.\.\." + with pytest.warns() as record: model.components.teacup_temperature = 3 + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" + stocks = model.run() assert np.all(stocks["Teacup Temperature"] == 3) diff --git a/tests/pytest_pysd/pytest_select_submodel.py b/tests/pytest_pysd/pytest_select_submodel.py index fba77029..bcec7b0e 100644 --- a/tests/pytest_pysd/pytest_select_submodel.py +++ b/tests/pytest_pysd/pytest_select_submodel.py @@ -1,9 +1,9 @@ - -import pytest +import re import shutil from pathlib import Path -import numpy as np +import pytest +import numpy as np import pysd from pysd.translators.vensim.vensim_file import VensimFile @@ -60,7 +60,7 @@ class TestSubmodel: "Lookup table dependencies", "Stateful objects integrated with the selected variables" ] - warning = "Selecting submodel, "\ + warn_message = "Selecting submodel, "\ + "to run the full model again use model.reload()" common_vars = { 'initial_time', 'time_step', 'final_time', 'time', 'saveper', 'stock' @@ -141,11 +141,12 @@ def test_select_submodel(self, model, variables, modules, assert "stock" in model._doc["Py Name"].to_list() # select submodel - with pytest.warns(UserWarning) as record: + with pytest.warns() as record: model.select_submodel(vars=variables, modules=modules) # assert warning - assert str(record[0].message) == self.warning + record = [str(r.message) for r in record] + assert self.warn_message in record # assert stateful elements change assert len(model._dynamicstateful_elements) == 1 @@ -170,16 +171,20 @@ def test_select_submodel(self, model, variables, modules, # running the model without redefining dependencies will # produce nan values assert "Exogenous components for the following variables are"\ - + " necessary but not given:" in str(record[-1].message) + + " necessary but not given:" in record[-1] assert "Please, set them before running the model using "\ - + "set_components method..." in str(record[-1].message) + + "set_components method..." in record[-1] for var in dep_vars: - assert var in str(record[-1].message) + assert var in record[-1] assert np.any(np.isnan(model.run())) # redefine dependencies warn_message = "Replacing a variable by a constant value." - with pytest.warns(UserWarning, match=warn_message): + with pytest.warns(UserWarning) as record: out = model.run(params=dep_vars) + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" assert not np.any(np.isnan(out)) # select submodel using contour values @@ -214,7 +219,8 @@ def test_select_submodel_copy(self, model, variables, modules, inplace=False) # assert warning - assert str(record[0].message) == self.warning + record = [str(r.message) for r in record] + assert self.warn_message in record # assert original stateful elements assert len(model._dynamicstateful_elements) == 2 @@ -254,16 +260,20 @@ def test_select_submodel_copy(self, model, variables, modules, # running the model without redefining dependencies will # produce nan values assert "Exogenous components for the following variables are"\ - + " necessary but not given:" in str(record[-1].message) + + " necessary but not given:" in record[-1] assert "Please, set them before running the model using "\ - + "set_components method..." in str(record[-1].message) + + "set_components method..." in record[-1] for var in dep_vars: - assert var in str(record[-1].message) + assert var in record[-1] assert np.any(np.isnan(model2.run())) # redefine dependencies warn_message = "Replacing a variable by a constant value." - with pytest.warns(UserWarning, match=warn_message): + with pytest.warns(UserWarning) as record: out = model2.run(params=dep_vars) + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" assert not np.any(np.isnan(out)) # select submodel using contour values diff --git a/tests/pytest_translators/pytest_split_views.py b/tests/pytest_translators/pytest_split_views.py index 88b316cc..a68d8c29 100644 --- a/tests/pytest_translators/pytest_split_views.py +++ b/tests/pytest_translators/pytest_split_views.py @@ -273,7 +273,7 @@ def test_read_vensim_split_model_warnings(self, model_file, subview_sep, @pytest.mark.parametrize( - "model_path,subview_sep,warning_message", + "model_path,subview_sep,warn_message", [ ( # warning_noviews Path("test-models/samples/teacup/teacup.mdl"), @@ -306,6 +306,10 @@ def model(self, shared_tmpdir, model_path, _root): shutil.copy(_root.joinpath(model_path), file) return file - def test_split_view_warnings(self, model, subview_sep, warning_message): - with pytest.warns(UserWarning, match=warning_message): + def test_split_view_warnings(self, model, subview_sep, warn_message): + with pytest.warns() as record: pysd.read_vensim(model, split_views=True, subview_sep=subview_sep) + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" diff --git a/tests/pytest_translators/pytest_vensim.py b/tests/pytest_translators/pytest_vensim.py index 7b2f46e4..6f3db342 100644 --- a/tests/pytest_translators/pytest_vensim.py +++ b/tests/pytest_translators/pytest_vensim.py @@ -1,9 +1,10 @@ -import pytest from pathlib import Path + +import pytest + from parsimonious import VisitationError import pysd - from pysd.translators.vensim.vensim_file import VensimFile from pysd.translators.vensim.vensim_element import Element @@ -113,9 +114,9 @@ def test_subscript_range_error(self, element, error_message): ) def test_complex_mapping(self, element, mapping): # parse the mapping - warning_message = r"Subscript mapping detected\. "\ + warn_message = r"Subscript mapping detected\. "\ r"This feature works only for simple cases\." - with pytest.warns(UserWarning, match=warning_message): + with pytest.warns(UserWarning, match=warn_message): out = element.parse() assert out.mapping == mapping diff --git a/tests/pytest_types/external/pytest_external.py b/tests/pytest_types/external/pytest_external.py index 8ec5a184..2afb92aa 100644 --- a/tests/pytest_types/external/pytest_external.py +++ b/tests/pytest_types/external/pytest_external.py @@ -1,7 +1,9 @@ import sys +import re +import importlib.util + import pytest -import importlib.util import numpy as np import xarray as xr @@ -1974,9 +1976,15 @@ def test_data_interp_h1dm_row(self, _root): final_coords=coords, py_name=py_name) - with pytest.warns(UserWarning, match="Not able to interpolate"): + with pytest.warns(UserWarning) as record: data.initialize() + warn_message = "Not able to interpolate" + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" + assert all(np.isnan(data.data.values)) def test_data_interp_h1dm_row2(self, _root): @@ -2006,9 +2014,15 @@ def test_data_interp_h1dm_row2(self, _root): final_coords=coords, py_name=py_name) - with pytest.warns(UserWarning, match="Not able to interpolate"): + with pytest.warns(UserWarning) as record: data.initialize() + warn_message = "Not able to interpolate" + assert any([ + re.match(warn_message, str(warn.message)) + for warn in record + ]), f"Couldn't match warning:\n{warn_message}" + assert not any(np.isnan(data.data.loc[:, "B"].values)) assert not any(np.isnan(data.data.loc[:, "C"].values)) assert all(np.isnan(data.data.loc[:, "D"].values)) diff --git a/tests/pytest_types/statefuls/pytest_statefuls.py b/tests/pytest_types/statefuls/pytest_statefuls.py index c7060f64..8251d52e 100644 --- a/tests/pytest_types/statefuls/pytest_statefuls.py +++ b/tests/pytest_types/statefuls/pytest_statefuls.py @@ -178,26 +178,22 @@ def test_delay_order(self): tstep=lambda: 0.5, py_name='delay5') - warning_message = "Delay time very small, casting delay order "\ - "from 3 to 2" - with pytest.warns(UserWarning, match=warning_message): + warn_message = "Delay time very small, casting delay order from 3 to 2" + with pytest.warns(UserWarning, match=warn_message): delay1.initialize() - warning_message = "Delay time very small, casting delay order "\ - "from 3 to 2" - with pytest.warns(UserWarning, match=warning_message): + with pytest.warns(UserWarning, match=warn_message): delay2.initialize() - warning_message = r"Casting delay order from 1\.5 to 1" - with pytest.warns(UserWarning, match=warning_message): + warn_message = r"Casting delay order from 1\.5 to 1" + with pytest.warns(UserWarning, match=warn_message): delay3.initialize() - warning_message = r"Casting delay order from 1\.5 to 1" - with pytest.warns(UserWarning, match=warning_message): + with pytest.warns(UserWarning, match=warn_message): delay4.initialize() - warning_message = r"Casting delay order from 1\.500000 to 2" - with pytest.warns(UserWarning, match=warning_message): + warn_message = r"Casting delay order from 1\.500000 to 2" + with pytest.warns(UserWarning, match=warn_message): delay5.initialize() def test_forecast(self):