From 5bd4c459e5997a9523672f341061fc5ab843064b Mon Sep 17 00:00:00 2001 From: Antti Soininen Date: Thu, 25 Jan 2024 17:01:02 +0100 Subject: [PATCH] Lift Python version upper limit Re spine-tools/Spine-Toolbox#2522 --- .github/workflows/run_unit_tests.yml | 2 +- CHANGELOG.md | 1 + pyproject.toml | 2 +- spinedb_api/helpers.py | 25 +++++++++++++++++++ spinedb_api/import_mapping/import_mapping.py | 5 ++-- spinedb_api/import_mapping/type_conversion.py | 10 +++----- tests/test_helpers.py | 22 ++++++++++++++++ 7 files changed, 57 insertions(+), 10 deletions(-) diff --git a/.github/workflows/run_unit_tests.yml b/.github/workflows/run_unit_tests.yml index 22a48065..ac42522b 100644 --- a/.github/workflows/run_unit_tests.yml +++ b/.github/workflows/run_unit_tests.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, windows-latest] - 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@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ce2b6bf..d24c84cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ but those functions and methods are pending deprecation. ### Changed +- Python 3.12 is now supported. - Objects and relationships have been replaced by *entities*. Zero-dimensional entities correspond to objects while multidimensional entities to relationships. diff --git a/pyproject.toml b/pyproject.toml index f4dd65bc..3969813e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ classifiers = [ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Operating System :: OS Independent", ] -requires-python = ">=3.8.1, <3.12" +requires-python = ">=3.8.1" dependencies = [ # v1.4 does not pass tests "sqlalchemy >=1.3, <1.4", diff --git a/spinedb_api/helpers.py b/spinedb_api/helpers.py index f2e92baa..bbbaeb17 100644 --- a/spinedb_api/helpers.py +++ b/spinedb_api/helpers.py @@ -886,3 +886,28 @@ def group_consecutive(list_of_numbers): for _k, g in groupby(enumerate(sorted(list_of_numbers)), lambda x: x[0] - x[1]): group = list(map(itemgetter(1), g)) yield group[0], group[-1] + + +_TRUTHS = {s.casefold() for s in ("yes", "true", "y", "t", "1")} +_FALSES = {s.casefold() for s in ("no", "false", "n", "f", "0")} + + +def string_to_bool(string): + """Converts string to boolean. + + Recognizes "yes", "true", "y", "t" and "1" as True, "no", "false", "n", "f" and "0" as False. + Case insensitive. + Raises Value error if value is not recognized. + + Args: + string (str): string to convert + + Returns: + bool: True or False + """ + string = string.casefold() + if string in _TRUTHS: + return True + if string in _FALSES: + return False + raise ValueError(string) diff --git a/spinedb_api/import_mapping/import_mapping.py b/spinedb_api/import_mapping/import_mapping.py index af20d677..8fc5b955 100644 --- a/spinedb_api/import_mapping/import_mapping.py +++ b/spinedb_api/import_mapping/import_mapping.py @@ -11,8 +11,9 @@ ###################################################################################################################### """ Contains import mappings for database items such as entities, entity classes and parameter values. """ -from distutils.util import strtobool from enum import auto, Enum, unique + +from spinedb_api.helpers import string_to_bool from spinedb_api.mapping import Mapping, Position, unflatten, is_pivoted from spinedb_api.exception import InvalidMappingComponent @@ -808,7 +809,7 @@ class ScenarioActiveFlagMapping(ImportMapping): def _import_row(self, source_data, state, mapped_data): scenario = state[ImportKey.SCENARIO_NAME] - active = bool(strtobool(str(source_data))) + active = string_to_bool(str(source_data)) mapped_data.setdefault("scenarios", set()).add((scenario, active)) diff --git a/spinedb_api/import_mapping/type_conversion.py b/spinedb_api/import_mapping/type_conversion.py index fea46b4b..5200ccaa 100644 --- a/spinedb_api/import_mapping/type_conversion.py +++ b/spinedb_api/import_mapping/type_conversion.py @@ -10,13 +10,11 @@ # this program. If not, see . ###################################################################################################################### -""" -Type conversion functions. - -""" +""" Type conversion functions. """ import re -from distutils.util import strtobool + +from spinedb_api.helpers import string_to_bool from spinedb_api.parameter_value import DateTime, Duration, ParameterValueFormatError @@ -81,7 +79,7 @@ class BooleanConvertSpec(ConvertSpec): RETURN_TYPE = bool def __call__(self, value): - return self.RETURN_TYPE(strtobool(str(value))) + return self.RETURN_TYPE(string_to_bool(str(value))) class IntegerSequenceDateTimeConvertSpec(ConvertSpec): diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 4ed0abf2..676c6d06 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -20,6 +20,7 @@ name_from_dimensions, name_from_elements, remove_credentials_from_url, + string_to_bool, ) @@ -75,5 +76,26 @@ def test_returns_latest_version(self): self.assertEqual(get_head_alembic_version(), "8b0eff478bcb") +class TestStringToBool(unittest.TestCase): + def test_truths(self): + self.assertTrue(string_to_bool("yes")) + self.assertTrue(string_to_bool("YES")) + self.assertTrue(string_to_bool("y")) + self.assertTrue(string_to_bool("true")) + self.assertTrue(string_to_bool("t")) + self.assertTrue(string_to_bool("1")) + + def test_falses(self): + self.assertFalse(string_to_bool("NO")) + self.assertFalse(string_to_bool("no")) + self.assertFalse(string_to_bool("n")) + self.assertFalse(string_to_bool("false")) + self.assertFalse(string_to_bool("f")) + self.assertFalse(string_to_bool("0")) + + def test_raises_value_error(self): + self.assertRaises(ValueError, string_to_bool, "no truth in this") + + if __name__ == "__main__": unittest.main()