From 4a11094e9b19a4aaffcc4367963fbb311a3b352b Mon Sep 17 00:00:00 2001 From: ibachar-es <52480906+ibachar-es@users.noreply.github.com> Date: Thu, 9 Jun 2022 09:15:29 +0300 Subject: [PATCH 01/11] Fix infinite recursion on Windows --- decouple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decouple.py b/decouple.py index b19ec86..51c3e55 100644 --- a/decouple.py +++ b/decouple.py @@ -216,7 +216,7 @@ def _find_file(self, path): # search the parent parent = os.path.dirname(path) - if parent and parent != os.path.abspath(os.sep): + if parent and os.path.normcase(parent) != os.path.normcase(os.path.abspath(os.sep)): return self._find_file(parent) # reached root without finding any files. From 81531225f014801795d91583b6232a6533c8ac7c Mon Sep 17 00:00:00 2001 From: ibachar-es Date: Sun, 13 Nov 2022 10:26:45 +0200 Subject: [PATCH 02/11] Added unit test --- tests/test_autoconfig.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_autoconfig.py b/tests/test_autoconfig.py index 75d97c3..94af7cc 100644 --- a/tests/test_autoconfig.py +++ b/tests/test_autoconfig.py @@ -97,3 +97,13 @@ def test_autoconfig_env_default_encoding(): assert config.encoding == DEFAULT_ENCODING assert 'ENV' == config('KEY', default='ENV') mopen.assert_called_once_with(filename, encoding=DEFAULT_ENCODING) + + +def test_autoconfig_no_repository(): + path = os.path.join(os.path.dirname(__file__), 'autoconfig', 'ini', 'no_repository') + config = AutoConfig(path) + + with pytest.raises(UndefinedValueError): + config('KeyNotInEnvAndNotInRepository') + + assert isinstance(config.config.repository, RepositoryEmpty) From afa46b14f24fd24cf3f6a8e32efa072130b60ac1 Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Thu, 5 Jan 2023 12:39:19 -0300 Subject: [PATCH 03/11] Fixed #149 Csv with None should return an empty list. --- decouple.py | 3 +++ tests/test_helper_csv.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/decouple.py b/decouple.py index b19ec86..31d6877 100644 --- a/decouple.py +++ b/decouple.py @@ -271,6 +271,9 @@ def __init__(self, cast=text_type, delimiter=',', strip=string.whitespace, post_ def __call__(self, value): """The actual transformation""" + if value is None: + return self.post_process() + transform = lambda s: self.cast(s.strip(self.strip)) splitter = shlex(value, posix=True) diff --git a/tests/test_helper_csv.py b/tests/test_helper_csv.py index 8d55a66..c5db287 100644 --- a/tests/test_helper_csv.py +++ b/tests/test_helper_csv.py @@ -29,3 +29,8 @@ def test_csv_quoted_parse(): assert ['foo', "'bar, baz'", "'qux"] == csv(''' foo ,"'bar, baz'", "'qux"''') assert ['foo', '"bar, baz"', '"qux'] == csv(""" foo ,'"bar, baz"', '"qux'""") + + +def test_csv_none(): + csv = Csv() + assert [] == csv(None) From 88739ab3f349db217c515462aed7b46c38b92cba Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 9 Jan 2023 16:42:43 -0300 Subject: [PATCH 04/11] Version bump: 3.7 --- CHANGELOG.md | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe44557..62e2163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Changelog ========= +3.7 (2023-01-09) +---------------- + +- Fix `Csv` cast hanging with `default=None`, now returning an empty list. (#149) + 3.6 (2022-02-02) ---------------- diff --git a/setup.py b/setup.py index e447851..2ea2c29 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.6', + version='3.7', description='Strict separation of settings from code.', long_description=open(README).read(), author="Henrique Bastos", author_email="henrique@bastos.net", From d596515ad9bde569c9f3971bce5a824f4b142adb Mon Sep 17 00:00:00 2001 From: Maddison Hellstrom Date: Wed, 15 Feb 2023 17:53:34 -0800 Subject: [PATCH 05/11] Raise KeyError when key is not found in ini repositories Enables RepositoryIni to be used in a collections.ChainMap to allow cascading multiple repositories. See #142 --- decouple.py | 9 ++++++--- tests/test_env.py | 4 ++++ tests/test_ini.py | 5 +++++ tests/test_secrets.py | 8 ++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/decouple.py b/decouple.py index 31d6877..f499862 100644 --- a/decouple.py +++ b/decouple.py @@ -11,10 +11,10 @@ if PYVERSION >= (3, 0, 0): - from configparser import ConfigParser + from configparser import ConfigParser, NoOptionError text_type = str else: - from ConfigParser import SafeConfigParser as ConfigParser + from ConfigParser import SafeConfigParser as ConfigParser, NoOptionError text_type = unicode if PYVERSION >= (3, 2, 0): @@ -134,7 +134,10 @@ def __contains__(self, key): self.parser.has_option(self.SECTION, key)) def __getitem__(self, key): - return self.parser.get(self.SECTION, key) + try: + return self.parser.get(self.SECTION, key) + except NoOptionError: + raise KeyError(key) class RepositoryEnv(RepositoryEmpty): diff --git a/tests/test_env.py b/tests/test_env.py index a259af3..a91c95c 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -136,3 +136,7 @@ def test_env_with_quote(config): assert '"Y"' == config('KeyHasTwoDoubleQuote') assert '''"Y\'''' == config('KeyHasMixedQuotesAsData1') assert '''\'Y"''' == config('KeyHasMixedQuotesAsData2') + +def test_env_repo_keyerror(config): + with pytest.raises(KeyError): + config.repository['UndefinedKey'] diff --git a/tests/test_ini.py b/tests/test_ini.py index 6ff0523..f610078 100644 --- a/tests/test_ini.py +++ b/tests/test_ini.py @@ -121,3 +121,8 @@ def test_ini_undefined_but_present_in_os_environ(config): def test_ini_empty_string_means_false(config): assert False is config('KeyEmpty', cast=bool) + + +def test_ini_repo_keyerror(config): + with pytest.raises(KeyError): + config.repository['UndefinedKey'] diff --git a/tests/test_secrets.py b/tests/test_secrets.py index 481fdeb..7d6185b 100644 --- a/tests/test_secrets.py +++ b/tests/test_secrets.py @@ -1,5 +1,6 @@ # coding: utf-8 import os +import pytest from decouple import Config, RepositorySecret @@ -28,3 +29,10 @@ def test_secret_overriden_by_environ(): os.environ['db_user'] = 'hi' assert 'hi' == config('db_user') del os.environ['db_user'] + +def test_secret_repo_keyerror(): + path = os.path.join(os.path.dirname(__file__), 'secrets') + repo = RepositorySecret(path) + + with pytest.raises(KeyError): + repo['UndefinedKey'] From 860969c0bc7ea9f6815447b498cbaf206813865b Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Wed, 1 Mar 2023 16:34:07 -0300 Subject: [PATCH 06/11] Bump version 3.8 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2ea2c29..e972783 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.7', + version='3.8', description='Strict separation of settings from code.', long_description=open(README).read(), author="Henrique Bastos", author_email="henrique@bastos.net", From 6d721d812069360859ea4d266a5551067e62a8b9 Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Mon, 17 Apr 2023 20:35:38 -0300 Subject: [PATCH 07/11] Add FAQ to README --- README.rst | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.rst b/README.rst index 6e36272..0885176 100644 --- a/README.rst +++ b/README.rst @@ -383,6 +383,52 @@ You can also use a Django-like choices tuple: >>> config('CONNECTION_TYPE', cast=Choices(choices=CONNECTION_OPTIONS)) 'bluetooth' +Frequently Asked Questions +========================== + +## 1. How to specify the `.env` path? + +```python +import os +from decouple import Config, RepositoryEnv +config = Config(RepositoryEnv("path/to/.env")) +``` + +## 2. How to use python-decouple with Jupyter? + +```python +import os +from decouple import Config, RepositoryEnv +config = Config(RepositoryEnv("path/to/.env")) +``` + +## 3. How to specify a file with another name instead of `.env`? + +```python +import os +from decouple import Config, RepositoryEnv +config = Config(RepositoryEnv("path/to/somefile-like-env")) +``` + +## 4. How to define the path to my env file on a env var? + +```python +import os +from decouple import Config, RepositoryEnv + +DOTENV_FILE = os.environ.get("DOTENV_FILE", ".env") # only place using os.environ +config = Config(RepositoryEnv(DOTENV_FILE)) +``` + +## 5. How can I have multiple *env* files working together? + +```python +from collections import ChainMap +from decouple import Config, RepositoryEnv + +config = Config(ChainMap(RepositoryEnv(".private.env"), RepositoryEnv(".env"))) +``` + Contribute ========== From 7c9b250c53bdd0ae96f449d5d2be918ed90ba273 Mon Sep 17 00:00:00 2001 From: Henrique Bastos Date: Mon, 17 Apr 2023 20:37:12 -0300 Subject: [PATCH 08/11] Update README.rst --- README.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 0885176..f9df531 100644 --- a/README.rst +++ b/README.rst @@ -386,7 +386,8 @@ You can also use a Django-like choices tuple: Frequently Asked Questions ========================== -## 1. How to specify the `.env` path? +1) How to specify the `.env` path? +---------------------------------- ```python import os @@ -394,7 +395,8 @@ from decouple import Config, RepositoryEnv config = Config(RepositoryEnv("path/to/.env")) ``` -## 2. How to use python-decouple with Jupyter? +2) How to use python-decouple with Jupyter? +------------------------------------------- ```python import os @@ -402,7 +404,8 @@ from decouple import Config, RepositoryEnv config = Config(RepositoryEnv("path/to/.env")) ``` -## 3. How to specify a file with another name instead of `.env`? +3) How to specify a file with another name instead of `.env`? +---------------------------------------------------------------- ```python import os @@ -410,7 +413,8 @@ from decouple import Config, RepositoryEnv config = Config(RepositoryEnv("path/to/somefile-like-env")) ``` -## 4. How to define the path to my env file on a env var? +4) How to define the path to my env file on a env var? +-------------------------------------------------------- ```python import os @@ -420,7 +424,8 @@ DOTENV_FILE = os.environ.get("DOTENV_FILE", ".env") # only place using os.enviro config = Config(RepositoryEnv(DOTENV_FILE)) ``` -## 5. How can I have multiple *env* files working together? +5) How can I have multiple *env* files working together? +-------------------------------------------------------- ```python from collections import ChainMap From 0a63a9e3501c83788ebeb837cc42f70f3aba19e9 Mon Sep 17 00:00:00 2001 From: Seyed Mehrshad Hosseini <56117015+mehrh8@users.noreply.github.com> Date: Thu, 4 May 2023 18:28:49 +0330 Subject: [PATCH 09/11] Update README.rst --- README.rst | 73 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index f9df531..021516e 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,4 @@ +======================================================== Python Decouple: Strict separation of settings from code ======================================================== @@ -22,8 +23,6 @@ for separating settings from code. :target: https://pypi.python.org/pypi/python-decouple/ :alt: Latest PyPI version - - .. contents:: Summary @@ -42,6 +41,7 @@ The first 2 are *project settings* and the last 3 are *instance settings*. You should be able to change *instance settings* without redeploying your app. + Why not just use environment variables? --------------------------------------- @@ -61,8 +61,9 @@ Since it's a non-empty string, it will be evaluated as True. *Decouple* provides a solution that doesn't look like a workaround: ``config('DEBUG', cast=bool)``. + Usage -===== +====== Install: @@ -88,6 +89,7 @@ Then use it on your ``settings.py``. EMAIL_HOST = config('EMAIL_HOST', default='localhost') EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) + Encodings --------- Decouple's default encoding is `UTF-8`. @@ -112,11 +114,13 @@ If you wish to fall back to your system's default encoding use: config.encoding = locale.getpreferredencoding(False) SECRET_KEY = config('SECRET_KEY') + Where is the settings data stored? ----------------------------------- *Decouple* supports both *.ini* and *.env* files. + Ini file ~~~~~~~~ @@ -134,6 +138,7 @@ Simply create a ``settings.ini`` next to your configuration module in the form: *Note*: Since ``ConfigParser`` supports *string interpolation*, to represent the character ``%`` you need to escape it as ``%%``. + Env file ~~~~~~~~ @@ -148,6 +153,7 @@ Simply create a ``.env`` text file in your repository's root directory in the fo PERCENTILE=90% #COMMENTED=42 + Example: How do I use it with Django? ------------------------------------- @@ -191,6 +197,7 @@ and `dj-database-url `_. # ... + Attention with *undefined* parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -201,6 +208,7 @@ If ``SECRET_KEY`` is not present in the ``.env``, *decouple* will raise an ``Und This *fail fast* policy helps you avoid chasing misbehaviours when you eventually forget a parameter. + Overriding config files with environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -297,6 +305,7 @@ Let's see some examples for the above mentioned cases: As you can see, ``cast`` is very flexible. But the last example got a bit complex. + Built in Csv Helper ~~~~~~~~~~~~~~~~~~~ @@ -340,6 +349,7 @@ By default *Csv* returns a ``list``, but you can get a ``tuple`` or whatever you >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) ('HTTP_X_FORWARDED_PROTO', 'https') + Built in Choices helper ~~~~~~~~~~~~~~~~~~~~~~~ @@ -383,56 +393,61 @@ You can also use a Django-like choices tuple: >>> config('CONNECTION_TYPE', cast=Choices(choices=CONNECTION_OPTIONS)) 'bluetooth' + Frequently Asked Questions ========================== + 1) How to specify the `.env` path? ---------------------------------- -```python -import os -from decouple import Config, RepositoryEnv -config = Config(RepositoryEnv("path/to/.env")) -``` +.. code-block:: python + + >>> import os + >>> from decouple import Config, RepositoryEnv + >>> config = Config(RepositoryEnv("path/to/.env")) + 2) How to use python-decouple with Jupyter? ------------------------------------------- -```python -import os -from decouple import Config, RepositoryEnv -config = Config(RepositoryEnv("path/to/.env")) -``` +.. code-block:: python + + >>> import os + >>> from decouple import Config, RepositoryEnv + >>> config = Config(RepositoryEnv("path/to/.env")) + 3) How to specify a file with another name instead of `.env`? ---------------------------------------------------------------- -```python -import os -from decouple import Config, RepositoryEnv -config = Config(RepositoryEnv("path/to/somefile-like-env")) -``` +.. code-block:: python + + >>> import os + >>> from decouple import Config, RepositoryEnv + >>> config = Config(RepositoryEnv("path/to/somefile-like-env")) + 4) How to define the path to my env file on a env var? -------------------------------------------------------- -```python -import os -from decouple import Config, RepositoryEnv +.. code-block:: python + + >>> import os + >>> from decouple import Config, RepositoryEnv + >>> DOTENV_FILE = os.environ.get("DOTENV_FILE", ".env") # only place using os.environ + >>> config = Config(RepositoryEnv(DOTENV_FILE)) -DOTENV_FILE = os.environ.get("DOTENV_FILE", ".env") # only place using os.environ -config = Config(RepositoryEnv(DOTENV_FILE)) -``` 5) How can I have multiple *env* files working together? -------------------------------------------------------- -```python -from collections import ChainMap -from decouple import Config, RepositoryEnv +.. code-block:: python + + >>> from collections import ChainMap + >>> from decouple import Config, RepositoryEnv + >>> config = Config(ChainMap(RepositoryEnv(".private.env"), RepositoryEnv(".env"))) -config = Config(ChainMap(RepositoryEnv(".private.env"), RepositoryEnv(".env"))) -``` Contribute ========== From 807476848694c5d99bd82a0d60332eb53fe1f2b5 Mon Sep 17 00:00:00 2001 From: Seyed Mehrshad Hosseini <56117015+mehrh8@users.noreply.github.com> Date: Thu, 4 May 2023 18:35:05 +0330 Subject: [PATCH 10/11] Update README.rst --- README.rst | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 021516e..9b45f34 100644 --- a/README.rst +++ b/README.rst @@ -403,9 +403,11 @@ Frequently Asked Questions .. code-block:: python - >>> import os - >>> from decouple import Config, RepositoryEnv - >>> config = Config(RepositoryEnv("path/to/.env")) + import os + from decouple import Config, RepositoryEnv + + + config = Config(RepositoryEnv("path/to/.env")) 2) How to use python-decouple with Jupyter? @@ -413,9 +415,11 @@ Frequently Asked Questions .. code-block:: python - >>> import os - >>> from decouple import Config, RepositoryEnv - >>> config = Config(RepositoryEnv("path/to/.env")) + import os + from decouple import Config, RepositoryEnv + + + config = Config(RepositoryEnv("path/to/.env")) 3) How to specify a file with another name instead of `.env`? @@ -423,9 +427,11 @@ Frequently Asked Questions .. code-block:: python - >>> import os - >>> from decouple import Config, RepositoryEnv - >>> config = Config(RepositoryEnv("path/to/somefile-like-env")) + import os + from decouple import Config, RepositoryEnv + + + config = Config(RepositoryEnv("path/to/somefile-like-env")) 4) How to define the path to my env file on a env var? @@ -433,10 +439,12 @@ Frequently Asked Questions .. code-block:: python - >>> import os - >>> from decouple import Config, RepositoryEnv - >>> DOTENV_FILE = os.environ.get("DOTENV_FILE", ".env") # only place using os.environ - >>> config = Config(RepositoryEnv(DOTENV_FILE)) + import os + from decouple import Config, RepositoryEnv + + + DOTENV_FILE = os.environ.get("DOTENV_FILE", ".env") # only place using os.environ + config = Config(RepositoryEnv(DOTENV_FILE)) 5) How can I have multiple *env* files working together? @@ -444,9 +452,11 @@ Frequently Asked Questions .. code-block:: python - >>> from collections import ChainMap - >>> from decouple import Config, RepositoryEnv - >>> config = Config(ChainMap(RepositoryEnv(".private.env"), RepositoryEnv(".env"))) + from collections import ChainMap + from decouple import Config, RepositoryEnv + + + config = Config(ChainMap(RepositoryEnv(".private.env"), RepositoryEnv(".env"))) Contribute From ddda0933a97998e308d2af45037654c929053509 Mon Sep 17 00:00:00 2001 From: Seyed Mehrshad Hosseini <56117015+mehrh8@users.noreply.github.com> Date: Thu, 4 May 2023 18:43:13 +0330 Subject: [PATCH 11/11] Update README.rst --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 9b45f34..4184536 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,7 @@ Since it's a non-empty string, it will be evaluated as True. Usage -====== +===== Install: @@ -92,6 +92,7 @@ Then use it on your ``settings.py``. Encodings --------- + Decouple's default encoding is `UTF-8`. But you can specify your preferred encoding. @@ -116,7 +117,7 @@ If you wish to fall back to your system's default encoding use: Where is the settings data stored? ------------------------------------ +---------------------------------- *Decouple* supports both *.ini* and *.env* files.