From: foamyguy Date: Tue, 4 Mar 2025 16:49:11 +0000 (-0600) Subject: Merge pull request #949 from justmobilize/settings-toml X-Git-Tag: 8.55.0 X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/commitdiff_plain/58a8e1716d6f424c11f10d8d967dcfb02e71d9ea?hp=c23dc24baec205cfadea478b66fda6cbad3392fd Merge pull request #949 from justmobilize/settings-toml Add load_settings_toml --- diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..891ae04 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2025 Justin Myers +# +# SPDX-License-Identifier: MIT +[pytest] +pythonpath = src diff --git a/setup.py b/setup.py index efeab2e..92ce579 100755 --- a/setup.py +++ b/setup.py @@ -99,6 +99,7 @@ setup( "pyftdi>=0.40.0", "adafruit-circuitpython-typing", "sysv_ipc>=1.1.0;sys_platform=='linux' and platform_machine!='mips'", + "toml>=0.10.2;python_version<'3.11'", ] + board_reqs, license="MIT", diff --git a/src/adafruit_blinka/__init__.py b/src/adafruit_blinka/__init__.py index a4ef215..38ccec4 100755 --- a/src/adafruit_blinka/__init__.py +++ b/src/adafruit_blinka/__init__.py @@ -8,6 +8,13 @@ * Author(s): cefn """ +import os + +try: + import tomllib +except ImportError: + import toml as tomllib + class Enum: """ @@ -74,6 +81,39 @@ class Lockable(ContextManaged): self._locked = False +def load_settings_toml(): + """Load values from settings.toml into os.environ, so that os.getenv returns them.""" + if not os.path.isfile("settings.toml"): + raise FileNotFoundError("settings.toml not cound in current directory.") + + print("settings.toml found. Updating environment variables:") + with open("settings.toml", "rb") as toml_file: + try: + settings = tomllib.load(toml_file) + except tomllib.TOMLDecodeError as e: + raise tomllib.TOMLDecodeError("Error with settings.toml file.") from e + + invalid_types = set() + for key, value in settings.items(): + if not isinstance(value, (bool, int, float, str)): + invalid_types.add(type(value).__name__) + if invalid_types: + invalid_types_string = ", ".join(invalid_types) + raise ValueError( + f"The types: '{invalid_types_string}' are not supported in settings.toml." + ) + + for key, value in settings.items(): + key = str(key) + if key in os.environ: + print(f" - {key} already exists in environment") + continue + os.environ[key] = str(value) + print(f" - {key} added") + + return settings + + def patch_system(): """Patch modules that may be different due to the platform.""" # pylint: disable=import-outside-toplevel diff --git a/tests/test_load_settings_toml.py b/tests/test_load_settings_toml.py new file mode 100644 index 0000000..4257108 --- /dev/null +++ b/tests/test_load_settings_toml.py @@ -0,0 +1,144 @@ +# SPDX-FileCopyrightText: 2025 Justin Myers +# +# SPDX-License-Identifier: MIT +import os +from unittest import mock +import pytest +from adafruit_blinka import load_settings_toml + +try: + import tomllib +except ImportError: + import toml as tomllib + +# pylint: disable=no-self-use,unused-argument + +CONVERTED_TOML = { + "123": 123, + "test": "test", + "test-hyphen": "test-hyphen", + "test_bool": True, + "test_number": 123, + "test_space": "test space", + "test_underscore": "test_underscore", + "true": False, +} + + +INVALID_TOML = b""" +# strings +test=test +""" + + +VALID_TOML = b""" +# strings +test="test" +test_space="test space" +test_underscore="test_underscore" +test-hyphen="test-hyphen" +# number +test_number=123 +# bool +test_bool=true +# other +123=123 +true=false +""" + +VALID_TOML_WITH_UNSUPPORTED_DATA_DICT = b""" +# dict +data = { key_1 = "value", key_2 = "value" } +""" + +VALID_TOML_WITH_UNSUPPORTED_DATA_LIST = b""" +# list +numbers = [ 1, 2, 3 ] +""" + +VALID_TOML_WITH_UNSUPPORTED_DATA_MANY = b""" +# dict +data = { key_1 = "value", key_2 = "value" } + +# list +numbers = [ 1, 2, 3 ] + +[nested] +test="test" +""" + +VALID_TOML_WITH_UNSUPPORTED_DATA_NESTED = b""" +[nested] +test="test" +""" + + +class TestLoadSettingsToml: + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=False)) + def test_raises_with_no_file(self): + with pytest.raises( + FileNotFoundError, match="settings.toml not cound in current directory." + ): + load_settings_toml() + + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=True)) + @mock.patch("builtins.open", mock.mock_open(read_data=INVALID_TOML)) + def test_raises_with_invalid_file(self): + with pytest.raises( + tomllib.TOMLDecodeError, match="Error with settings.toml file." + ): + load_settings_toml() + + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=True)) + @mock.patch( + "builtins.open", mock.mock_open(read_data=VALID_TOML_WITH_UNSUPPORTED_DATA_DICT) + ) + def test_raises_with_invalid_file_dict(self): + with pytest.raises( + ValueError, match="The types: 'dict' are not supported in settings.toml." + ): + load_settings_toml() + + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=True)) + @mock.patch( + "builtins.open", mock.mock_open(read_data=VALID_TOML_WITH_UNSUPPORTED_DATA_LIST) + ) + def test_raises_with_invalid_file_list(self): + with pytest.raises( + ValueError, match="The types: 'list' are not supported in settings.toml." + ): + load_settings_toml() + + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=True)) + @mock.patch( + "builtins.open", mock.mock_open(read_data=VALID_TOML_WITH_UNSUPPORTED_DATA_MANY) + ) + def test_raises_with_invalid_file_many(self): + with pytest.raises( + ValueError, + match="The types: 'dict, list' are not supported in settings.toml.", + ): + load_settings_toml() + + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=True)) + @mock.patch( + "builtins.open", + mock.mock_open(read_data=VALID_TOML_WITH_UNSUPPORTED_DATA_NESTED), + ) + def test_raises_with_invalid_file_nested(self): + with pytest.raises( + ValueError, match="The types: 'dict' are not supported in settings.toml." + ): + load_settings_toml() + + @mock.patch("adafruit_blinka.os.path.isfile", mock.Mock(return_value=True)) + @mock.patch("builtins.open", mock.mock_open(read_data=VALID_TOML)) + @mock.patch.dict(os.environ, {}, clear=True) + def test_returns_data(self): + for key in CONVERTED_TOML: + assert os.getenv(key) is None + + assert load_settings_toml() == CONVERTED_TOML + + for key, value in CONVERTED_TOML.items(): + assert os.getenv(key) == str(value)