From 90b0bb8849131e782525602128aaf3a53379cc3d Mon Sep 17 00:00:00 2001 From: Zandor Sabino Date: Mon, 29 Nov 2021 11:34:03 -0300 Subject: [PATCH 01/11] fix: replace strtobool for local function --- decouple.py | 2 +- tests/test_util.py | 21 +++++++++++++++++++++ util.py | 9 +++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/test_util.py create mode 100644 util.py diff --git a/decouple.py b/decouple.py index 302b864..8f42287 100644 --- a/decouple.py +++ b/decouple.py @@ -5,7 +5,7 @@ from shlex import shlex from io import open from collections import OrderedDict -from distutils.util import strtobool +from util import strtobool # Useful for very coarse version differentiation. PYVERSION = sys.version_info diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 0000000..b774555 --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,21 @@ +import pytest +from util import strtobool + + +def test_true_values(): + true_list = ["y", "yes", "t", "true", "on", "1"] + for item in true_list: + assert strtobool(item) == 1 + + +def test_false_values(): + false_list = ["n", "no", "f", "false", "off", "0"] + for item in false_list: + assert strtobool(item) == 0 + + +def test_invalid_value_text(): + invalid_list = ["Invalid_Value_1", "1nv4l1d_V4lu3_2", "Invalid_Value_3"] + for value in invalid_list: + with pytest.raises(ValueError, match="invalid truth value '%s'".format(value)): + strtobool(value) diff --git a/util.py b/util.py new file mode 100644 index 0000000..7551a7c --- /dev/null +++ b/util.py @@ -0,0 +1,9 @@ +def strtobool(value): + value = value.lower() + if value in ["y", "yes", "t", "true", "on", "1"]: + result = True + elif value in ["n", "no", "f", "false", "off", "0"]: + result = False + else: + raise ValueError("invalid truth value '%s'".format(value)) + return result From 2b62a7068f98aeb3cb20c9d861a896fcdcd5c6a1 Mon Sep 17 00:00:00 2001 From: Steve Jalim Date: Mon, 6 Dec 2021 17:02:25 +0000 Subject: [PATCH 02/11] Update Changelog to reflect latest versions For full details, see https://github.com/henriquebastos/python-decouple/compare/v3.3...v3.5 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d39765..83e2fca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ Changelog ========= +3.5 (2021-09-30) +---------------- + +- Fix: fix syntax warnings due to comparison of literals using `is` +- Fix: avoid DeprecationError on ConfigParser.readfp() +- Add Tox Github Action +- Documentation fixups +- Security: bump Pygments version to >=2.7.4 +- Fix .env -file quote stripping +- Changelog catchups for 3.2 and 3.3 + + +3.4 (2021-01-05) +---------------- + +- Add choices helper +- Documentation fixups + + 3.3 (2019-11-13) ---------------- From 26982e142bd321ccd2722c8aa0c5cf0cc65e7a3e Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 18 Jun 2018 18:20:12 +1200 Subject: [PATCH 03/11] Support for docker secrets --- decouple.py | 22 ++++++++++++++++++++++ tests/secrets/db_password | 1 + tests/secrets/db_user | 1 + tests/test_secrets.py | 21 +++++++++++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 tests/secrets/db_password create mode 100644 tests/secrets/db_user create mode 100644 tests/test_secrets.py diff --git a/decouple.py b/decouple.py index 302b864..d2070c1 100644 --- a/decouple.py +++ b/decouple.py @@ -146,6 +146,28 @@ def __getitem__(self, key): return self.data[key] +class RepositorySecret(RepositoryEmpty): + """ + Retrieves option keys from files, + where title of file is a key, content of file is a value + e.g. Docker swarm secrets + """ + + def __init__(self, source='/run/secrets/'): + self.data = {} + + ls = os.listdir(source) + for file in ls: + with open(os.path.join(source, file), 'r') as f: + self.data[file] = f.read() + + def __contains__(self, key): + return key in os.environ or key in self.data + + def __getitem__(self, key): + return self.data[key] + + class AutoConfig(object): """ Autodetects the config file and type. diff --git a/tests/secrets/db_password b/tests/secrets/db_password new file mode 100644 index 0000000..04fea06 --- /dev/null +++ b/tests/secrets/db_password @@ -0,0 +1 @@ +world \ No newline at end of file diff --git a/tests/secrets/db_user b/tests/secrets/db_user new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/tests/secrets/db_user @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/tests/test_secrets.py b/tests/test_secrets.py new file mode 100644 index 0000000..766a99a --- /dev/null +++ b/tests/test_secrets.py @@ -0,0 +1,21 @@ +# coding: utf-8 +import os + +from decouple import Config, RepositorySecret + + +def test_secrets(): + path = os.path.join(os.path.dirname(__file__), 'secrets') + config = Config(RepositorySecret(path)) + + assert 'hello' == config('db_user') + assert 'world' == config('db_password') + + +def test_env_undefined_but_present_in_os_environ(): + path = os.path.join(os.path.dirname(__file__), 'secrets') + config = Config(RepositorySecret(path)) + + os.environ['KeyOnlyEnviron'] = '' + assert '' == config('KeyOnlyEnviron') + del os.environ['KeyOnlyEnviron'] From e227ab62569334e4dd0d67bb4b9639f35fa1e6fb Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Sat, 11 Dec 2021 09:44:25 -0300 Subject: [PATCH 04/11] Improve secret tests. --- tests/test_secrets.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_secrets.py b/tests/test_secrets.py index 766a99a..481fdeb 100644 --- a/tests/test_secrets.py +++ b/tests/test_secrets.py @@ -12,10 +12,19 @@ def test_secrets(): assert 'world' == config('db_password') -def test_env_undefined_but_present_in_os_environ(): +def test_no_secret_but_present_in_os_environ(): path = os.path.join(os.path.dirname(__file__), 'secrets') config = Config(RepositorySecret(path)) - os.environ['KeyOnlyEnviron'] = '' - assert '' == config('KeyOnlyEnviron') + os.environ['KeyOnlyEnviron'] = 'SOMETHING' + assert 'SOMETHING' == config('KeyOnlyEnviron') del os.environ['KeyOnlyEnviron'] + + +def test_secret_overriden_by_environ(): + path = os.path.join(os.path.dirname(__file__), 'secrets') + config = Config(RepositorySecret(path)) + + os.environ['db_user'] = 'hi' + assert 'hi' == config('db_user') + del os.environ['db_user'] From dd78373935d675d97bab1123579f7af37c9097c6 Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Sat, 11 Dec 2021 09:44:37 -0300 Subject: [PATCH 05/11] Update changelog. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83e2fca..dab826a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Changelog ========= +3.6 (dev) +--------- + +- Add support for Docker secrets. 3.5 (2021-09-30) ---------------- From 01376062194428e286b8916cbdb7008684db0350 Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Sat, 11 Dec 2021 09:47:47 -0300 Subject: [PATCH 06/11] Add twine to requirements to enable release workflow --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index d7173e4..b47df22 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ pytest==3.2.0 tox==2.7.0 docutils==0.14 Pygments>=2.7.4 +twine From 7e90cd38a3e2693412674a96e2e2ceb95c3dbe71 Mon Sep 17 00:00:00 2001 From: Zandor Sabino Date: Sat, 11 Dec 2021 22:36:48 -0300 Subject: [PATCH 07/11] join utils in decouple --- decouple.py | 14 ++++++++++++-- tests/test_strtobool.py | 28 ++++++++++++++++++++++++++++ tests/test_util.py | 21 --------------------- util.py | 9 --------- 4 files changed, 40 insertions(+), 32 deletions(-) create mode 100644 tests/test_strtobool.py delete mode 100644 tests/test_util.py delete mode 100644 util.py diff --git a/decouple.py b/decouple.py index 8f42287..847b530 100644 --- a/decouple.py +++ b/decouple.py @@ -5,7 +5,6 @@ from shlex import shlex from io import open from collections import OrderedDict -from util import strtobool # Useful for very coarse version differentiation. PYVERSION = sys.version_info @@ -26,6 +25,18 @@ DEFAULT_ENCODING = 'UTF-8' + +def strtobool(value): + _value = value.lower() + if _value in {"y", "yes", "t", "true", "on", "1"}: + result = True + elif _value in {"n", "no", "f", "false", "off", "0"}: + result = False + else: + raise ValueError(" ".join(("invalid truth value", value))) + return result + + class UndefinedValueError(Exception): pass @@ -261,7 +272,6 @@ def __init__(self, flat=None, cast=text_type, choices=None): self._valid_values.extend(self.flat) self._valid_values.extend([value for value, _ in self.choices]) - def __call__(self, value): transform = self.cast(value) if transform not in self._valid_values: diff --git a/tests/test_strtobool.py b/tests/test_strtobool.py new file mode 100644 index 0000000..cfe4bf0 --- /dev/null +++ b/tests/test_strtobool.py @@ -0,0 +1,28 @@ +import pytest +from decouple import strtobool + + +def test_true_values(): + true_list = ["y", "yes", "t", "true", "on", "1"] + for item in true_list: + assert strtobool(item) == 1 + + +def test_false_values(): + false_list = ["n", "no", "f", "false", "off", "0"] + for item in false_list: + assert strtobool(item) == 0 + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ("Invalid_Value_1", "invalid truth value Invalid_Value_1"), + ("1nv4l1d_V4lu3_2", "invalid truth value 1nv4l1d_V4lu3_2"), + ("invalid_value_3", "invalid truth value invalid_value_3"), + ], +) +def test_eval(test_input, expected): + with pytest.raises(ValueError) as execinfo: + strtobool(test_input) + assert str(execinfo.value) == expected diff --git a/tests/test_util.py b/tests/test_util.py deleted file mode 100644 index b774555..0000000 --- a/tests/test_util.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest -from util import strtobool - - -def test_true_values(): - true_list = ["y", "yes", "t", "true", "on", "1"] - for item in true_list: - assert strtobool(item) == 1 - - -def test_false_values(): - false_list = ["n", "no", "f", "false", "off", "0"] - for item in false_list: - assert strtobool(item) == 0 - - -def test_invalid_value_text(): - invalid_list = ["Invalid_Value_1", "1nv4l1d_V4lu3_2", "Invalid_Value_3"] - for value in invalid_list: - with pytest.raises(ValueError, match="invalid truth value '%s'".format(value)): - strtobool(value) diff --git a/util.py b/util.py deleted file mode 100644 index 7551a7c..0000000 --- a/util.py +++ /dev/null @@ -1,9 +0,0 @@ -def strtobool(value): - value = value.lower() - if value in ["y", "yes", "t", "true", "on", "1"]: - result = True - elif value in ["n", "no", "f", "false", "off", "0"]: - result = False - else: - raise ValueError("invalid truth value '%s'".format(value)) - return result From a4b104d512673a58f00dd86e4096b228182453df Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Wed, 2 Feb 2022 15:47:23 -0300 Subject: [PATCH 08/11] Refactor PR #128 --- decouple.py | 20 ++++++++++++-------- tests/test_strtobool.py | 26 ++++++++------------------ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/decouple.py b/decouple.py index 847b530..b2876c1 100644 --- a/decouple.py +++ b/decouple.py @@ -26,15 +26,19 @@ DEFAULT_ENCODING = 'UTF-8' +# Python 3.10 don't have strtobool anymore. So we move it here. +TRUE_VALUES = {"y", "yes", "t", "true", "on", "1"} +FALSE_VALUES = {"n", "no", "f", "false", "off", "0"} + def strtobool(value): - _value = value.lower() - if _value in {"y", "yes", "t", "true", "on", "1"}: - result = True - elif _value in {"n", "no", "f", "false", "off", "0"}: - result = False - else: - raise ValueError(" ".join(("invalid truth value", value))) - return result + value = value.lower() + + if value in TRUE_VALUES: + return True + elif value in FALSE_VALUES: + return False + + raise ValueError("Invalid truth value: " + value) class UndefinedValueError(Exception): diff --git a/tests/test_strtobool.py b/tests/test_strtobool.py index cfe4bf0..d910d86 100644 --- a/tests/test_strtobool.py +++ b/tests/test_strtobool.py @@ -3,26 +3,16 @@ def test_true_values(): - true_list = ["y", "yes", "t", "true", "on", "1"] - for item in true_list: - assert strtobool(item) == 1 + for item in ("Y", "YES", "T", "TRUE", "ON", "1"): + assert strtobool(item) def test_false_values(): - false_list = ["n", "no", "f", "false", "off", "0"] - for item in false_list: - assert strtobool(item) == 0 + for item in ("N", "NO", "F", "FALSE", "OFF", "0"): + assert strtobool(item) is False -@pytest.mark.parametrize( - "test_input,expected", - [ - ("Invalid_Value_1", "invalid truth value Invalid_Value_1"), - ("1nv4l1d_V4lu3_2", "invalid truth value 1nv4l1d_V4lu3_2"), - ("invalid_value_3", "invalid truth value invalid_value_3"), - ], -) -def test_eval(test_input, expected): - with pytest.raises(ValueError) as execinfo: - strtobool(test_input) - assert str(execinfo.value) == expected +def test_invalid(): + with pytest.raises(ValueError, match="Invalid truth value"): + strtobool("MAYBE") + From f8024474aad70b9be32e0b00eee283b6b6ffa426 Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Wed, 2 Feb 2022 15:53:50 -0300 Subject: [PATCH 09/11] Parametrize tests --- tests/test_strtobool.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_strtobool.py b/tests/test_strtobool.py index d910d86..ba39930 100644 --- a/tests/test_strtobool.py +++ b/tests/test_strtobool.py @@ -2,14 +2,14 @@ from decouple import strtobool -def test_true_values(): - for item in ("Y", "YES", "T", "TRUE", "ON", "1"): - assert strtobool(item) +@pytest.mark.parametrize("value", ("Y", "YES", "T", "TRUE", "ON", "1")) +def test_true_values(value): + assert strtobool(value) -def test_false_values(): - for item in ("N", "NO", "F", "FALSE", "OFF", "0"): - assert strtobool(item) is False +@pytest.mark.parametrize("value", ("N", "NO", "F", "FALSE", "OFF", "0")) +def test_false_values(value): + assert strtobool(value) is False def test_invalid(): From a75fdfc073f6c2284ac36b61b2b34a6e414e1b48 Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Wed, 2 Feb 2022 15:59:26 -0300 Subject: [PATCH 10/11] Bump version 3.6 --- CHANGELOG.md | 5 +++-- setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dab826a..e7b6458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ Changelog ========= -3.6 (dev) ---------- +3.6 (2022-02-02) +---------------- - Add support for Docker secrets. +- Fix deprecation warning on Python 3.10 3.5 (2021-09-30) ---------------- diff --git a/setup.py b/setup.py index 9e2686b..e447851 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ README = os.path.join(os.path.dirname(__file__), 'README.rst') setup(name='python-decouple', - version='3.5', + version='3.6', description='Strict separation of settings from code.', long_description=open(README).read(), author="Henrique Bastos", author_email="henrique@bastos.net", From 4f16fbe42c5c7425492a2360542003bb1a40d9db Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Wed, 2 Feb 2022 16:07:12 -0300 Subject: [PATCH 11/11] Update workflow to python 3.10. --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94f9505..a8b6a17 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,9 +20,9 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" - py37: + py310: runs-on: ubuntu-latest - container: python:3.7-alpine + container: python:3.10-alpine # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -35,7 +35,7 @@ jobs: - name: Run Tox env: - TOXENV: py37 + TOXENV: py310 run: | "$HOME/.local/bin/tox"