diff -Nru python-decouple-3.1/debian/changelog python-decouple-3.6/debian/changelog --- python-decouple-3.1/debian/changelog 2018-11-13 13:13:31.000000000 +0000 +++ python-decouple-3.6/debian/changelog 2022-05-19 04:23:37.000000000 +0000 @@ -1,3 +1,16 @@ +python-decouple (3.6-1) unstable; urgency=medium + + * New maintainer upload (Closes: #986939) + * New upstream version 3.6 + * Lintian fixes + * debian/control: + - Use github homepage + - Bump Standards-Version: from 4.2.1 to 4.6.1 + * debian/rules: + - Move cleaning the egg-info dir to d/clean + + -- Matt Barry Thu, 19 May 2022 00:23:37 -0400 + python-decouple (3.1-4) unstable; urgency=medium [ Ondřej Nový ] diff -Nru python-decouple-3.1/debian/clean python-decouple-3.6/debian/clean --- python-decouple-3.1/debian/clean 1970-01-01 00:00:00.000000000 +0000 +++ python-decouple-3.6/debian/clean 2022-05-19 04:23:37.000000000 +0000 @@ -0,0 +1 @@ +python_decouple.egg-info/ diff -Nru python-decouple-3.1/debian/compat python-decouple-3.6/debian/compat --- python-decouple-3.1/debian/compat 2018-11-13 13:12:56.000000000 +0000 +++ python-decouple-3.6/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru python-decouple-3.1/debian/control python-decouple-3.6/debian/control --- python-decouple-3.1/debian/control 2018-11-13 13:13:31.000000000 +0000 +++ python-decouple-3.6/debian/control 2022-05-19 04:23:37.000000000 +0000 @@ -1,17 +1,18 @@ Source: python-decouple Priority: optional -Maintainer: Herbert Parentes Fortes Neto -Build-Depends: debhelper (>= 11), dh-python, python3-all, python3-setuptools -Standards-Version: 4.2.1 +Maintainer: Matt Barry +Build-Depends: debhelper-compat (= 12), dh-python, python3-all, python3-setuptools +Standards-Version: 4.6.0.1 Section: python -Homepage: https://pypi.python.org/pypi/python-decouple/ +Homepage: https://github.com/henriquebastos/python-decouple Vcs-Git: https://salsa.debian.org/debian/python-decouple.git Vcs-Browser: https://salsa.debian.org/debian/python-decouple +Rules-Requires-Root: no Package: python3-decouple Architecture: all Depends: ${misc:Depends}, ${python3:Depends} -Description: Helps you to organize your Django|Flask settings +Description: Helps you to organize your Django/Flask settings Decouple helps you to organize your settings so that you can change parameters without having to redeploy your app. . diff -Nru python-decouple-3.1/debian/copyright python-decouple-3.6/debian/copyright --- python-decouple-3.1/debian/copyright 2018-11-13 13:13:31.000000000 +0000 +++ python-decouple-3.6/debian/copyright 2022-05-19 04:23:37.000000000 +0000 @@ -1,6 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: python-decouple Source: https://pypi.python.org/pypi/python-decouple/ +Upstream-Contact: Henrique Bastos Files: * Copyright: 2017 Henrique Bastos @@ -8,6 +9,7 @@ Files: debian/* Copyright: 2017-2018 Herbert Parentes Fortes Neto + 2022 Matt Barry License: MIT License: MIT diff -Nru python-decouple-3.1/debian/rules python-decouple-3.6/debian/rules --- python-decouple-3.1/debian/rules 2018-11-13 13:12:56.000000000 +0000 +++ python-decouple-3.6/debian/rules 2022-05-19 04:23:37.000000000 +0000 @@ -4,8 +4,3 @@ %: dh $@ --with python3 --buildsystem=pybuild - - -override_dh_clean: - rm -f python_decouple.egg-info/* - dh_clean diff -Nru python-decouple-3.1/decouple.py python-decouple-3.6/decouple.py --- python-decouple-3.1/decouple.py 2017-08-07 23:00:56.000000000 +0000 +++ python-decouple-3.6/decouple.py 2022-02-02 19:25:13.000000000 +0000 @@ -3,18 +3,43 @@ import sys import string from shlex import shlex - +from io import open +from collections import OrderedDict # Useful for very coarse version differentiation. -PY3 = sys.version_info[0] == 3 +PYVERSION = sys.version_info + -if PY3: +if PYVERSION >= (3, 0, 0): from configparser import ConfigParser text_type = str else: from ConfigParser import SafeConfigParser as ConfigParser text_type = unicode +if PYVERSION >= (3, 2, 0): + read_config = lambda parser, file: parser.read_file(file) +else: + read_config = lambda parser, file: parser.readfp(file) + + +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 TRUE_VALUES: + return True + elif value in FALSE_VALUES: + return False + + raise ValueError("Invalid truth value: " + value) + class UndefinedValueError(Exception): pass @@ -35,8 +60,6 @@ """ Handle .env file format used by Foreman. """ - _BOOLEANS = {'1': True, 'yes': True, 'true': True, 'on': True, - '0': False, 'no': False, 'false': False, 'off': False, '': False} def __init__(self, repository): self.repository = repository @@ -46,10 +69,7 @@ Helper to convert config values to boolean as ConfigParser do. """ value = str(value) - if value.lower() not in self._BOOLEANS: - raise ValueError('Not a boolean: %s' % value) - - return self._BOOLEANS[value.lower()] + return bool(value) if value == '' else bool(strtobool(value)) @staticmethod def _cast_do_nothing(value): @@ -86,7 +106,7 @@ class RepositoryEmpty(object): - def __init__(self, source=''): + def __init__(self, source='', encoding=DEFAULT_ENCODING): pass def __contains__(self, key): @@ -102,10 +122,10 @@ """ SECTION = 'settings' - def __init__(self, source): + def __init__(self, source, encoding=DEFAULT_ENCODING): self.parser = ConfigParser() - with open(source) as file_: - self.parser.readfp(file_) + with open(source, encoding=encoding) as file_: + read_config(self.parser, file_) def __contains__(self, key): return (key in os.environ or @@ -119,17 +139,19 @@ """ Retrieves option keys from .env files with fall back to os.environ. """ - def __init__(self, source): + def __init__(self, source, encoding=DEFAULT_ENCODING): self.data = {} - with open(source) as file_: + with open(source, encoding=encoding) as file_: for line in file_: line = line.strip() if not line or line.startswith('#') or '=' not in line: continue k, v = line.split('=', 1) k = k.strip() - v = v.strip().strip('\'"') + v = v.strip() + if len(v) >= 2 and ((v[0] == "'" and v[-1] == "'") or (v[0] == '"' and v[-1] == '"')): + v = v[1:-1] self.data[k] = v def __contains__(self, key): @@ -139,6 +161,28 @@ 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. @@ -150,10 +194,12 @@ caller's path. """ - SUPPORTED = { - 'settings.ini': RepositoryIni, - '.env': RepositoryEnv, - } + SUPPORTED = OrderedDict([ + ('settings.ini', RepositoryIni), + ('.env', RepositoryEnv), + ]) + + encoding = DEFAULT_ENCODING def __init__(self, search_path=None): self.search_path = search_path @@ -168,7 +214,7 @@ # search the parent parent = os.path.dirname(path) - if parent and parent != os.path.sep: + if parent and parent != os.path.abspath(os.sep): return self._find_file(parent) # reached root without finding any files. @@ -182,7 +228,7 @@ filename = '' Repository = self.SUPPORTED.get(os.path.basename(filename), RepositoryEmpty) - self.config = Config(Repository(filename)) + self.config = Config(Repository(filename, encoding=self.encoding)) def _caller_path(self): # MAGIC! Get the caller's module path. @@ -201,7 +247,6 @@ # now just import config and start using with no configuration. config = AutoConfig() - # Helpers class Csv(object): @@ -215,7 +260,7 @@ cast -- callable that transforms the item just before it's added to the list. delimiter -- string of delimiters chars passed to shlex. strip -- string of non-relevant characters to be passed to str.strip after the split. - tuple_ -- boolean to check if it is to return in tuple format. + post_process -- callable to post process all casted values. Default is `list`. """ self.cast = cast self.delimiter = delimiter @@ -231,3 +276,33 @@ splitter.whitespace_split = True return self.post_process(transform(s) for s in splitter) + + +class Choices(object): + """ + Allows for cast and validation based on a list of choices. + """ + + def __init__(self, flat=None, cast=text_type, choices=None): + """ + Parameters: + flat -- a flat list of valid choices. + cast -- callable that transforms value before validation. + choices -- tuple of Django-like choices. + """ + self.flat = flat or [] + self.cast = cast + self.choices = choices or [] + + self._valid_values = [] + 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: + raise ValueError(( + 'Value not in list: {!r}; valid values are {!r}' + ).format(value, self._valid_values)) + else: + return transform diff -Nru python-decouple-3.1/LICENSE python-decouple-3.6/LICENSE --- python-decouple-3.1/LICENSE 1970-01-01 00:00:00.000000000 +0000 +++ python-decouple-3.6/LICENSE 2022-02-02 19:25:13.000000000 +0000 @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2013 Henrique Bastos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff -Nru python-decouple-3.1/MANIFEST.in python-decouple-3.6/MANIFEST.in --- python-decouple-3.1/MANIFEST.in 1970-01-01 00:00:00.000000000 +0000 +++ python-decouple-3.6/MANIFEST.in 2022-02-02 19:25:13.000000000 +0000 @@ -0,0 +1 @@ +include LICENSE diff -Nru python-decouple-3.1/PKG-INFO python-decouple-3.6/PKG-INFO --- python-decouple-3.1/PKG-INFO 2017-08-07 23:11:53.000000000 +0000 +++ python-decouple-3.6/PKG-INFO 2022-02-02 19:25:28.530947200 +0000 @@ -1,378 +1,11 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: python-decouple -Version: 3.1 +Version: 3.6 Summary: Strict separation of settings from code. Home-page: http://github.com/henriquebastos/python-decouple/ Author: Henrique Bastos Author-email: henrique@bastos.net License: MIT -Description: Python Decouple: Strict separation of settings from code - ======================================================== - - *Decouple* helps you to organize your settings so that you can - change parameters without having to redeploy your app. - - It also makes easy for you to: - - #. store parameters on *ini* or *.env* files; - #. define comprehensive default values; - #. properly convert values to the correct data type; - #. have **only one** configuration module to rule all your instances. - - It was originally designed for Django, but became an independent generic tool - for separating settings from code. - - .. image:: https://img.shields.io/travis/henriquebastos/python-decouple.svg - :target: https://travis-ci.org/henriquebastos/python-decouple - :alt: Build Status - - .. image:: https://landscape.io/github/henriquebastos/python-decouple/master/landscape.png - :target: https://landscape.io/github/henriquebastos/python-decouple/master - :alt: Code Health - - .. image:: https://img.shields.io/pypi/v/python-decouple.svg - :target: https://pypi.python.org/pypi/python-decouple/ - :alt: Latest PyPI version - - - - .. contents:: Summary - - - Why? - ==== - - Web framework's settings stores many different kinds of parameters: - - * Locale and i18n; - * Middlewares and Installed Apps; - * Resource handles to the database, Memcached, and other backing services; - * Credentials to external services such as Amazon S3 or Twitter; - * Per-deploy values such as the canonical hostname for the instance. - - The first 2 are *project settings* the last 3 are *instance settings*. - - You should be able to change *instance settings* without redeploying your app. - - Why not just use environment variables? - --------------------------------------- - - *Envvars* works, but since ``os.environ`` only returns strings, it's tricky. - - Let's say you have an *envvar* ``DEBUG=False``. If you run: - - .. code-block:: python - - if os.environ['DEBUG']: - print True - else: - print False - - It will print **True**, because ``os.environ['DEBUG']`` returns the **string** ``"False"``. - 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: - - .. code-block:: console - - pip install python-decouple - - - Then use it on your ``settings.py``. - - #. Import the ``config`` object: - - .. code-block:: python - - from decouple import config - - #. Retrieve the configuration parameters: - - .. code-block:: python - - SECRET_KEY = config('SECRET_KEY') - DEBUG = config('DEBUG', default=False, cast=bool) - EMAIL_HOST = config('EMAIL_HOST', default='localhost') - EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) - - Where the settings data are stored? - ----------------------------------- - - *Decouple* supports both *.ini* and *.env* files. - - Ini file - ~~~~~~~~ - - Simply create a ``settings.ini`` next to your configuration module in the form: - - .. code-block:: ini - - [settings] - DEBUG=True - TEMPLATE_DEBUG=%(DEBUG)s - SECRET_KEY=ARANDOMSECRETKEY - DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase - PERCENTILE=90%% - #COMMENTED=42 - - *Note*: Since ``ConfigParser`` supports *string interpolation*, to represent the character ``%`` you need to escape it as ``%%``. - - Env file - ~~~~~~~~ - - Simply create a ``.env`` text file on your repository's root directory in the form: - - .. code-block:: console - - DEBUG=True - TEMPLATE_DEBUG=True - SECRET_KEY=ARANDOMSECRETKEY - DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase - PERCENTILE=90% - #COMMENTED=42 - - Example: How do I use it with Django? - ------------------------------------- - - Given that I have a ``.env`` file at my repository root directory, here is a snippet of my ``settings.py``. - - I also recommend using `pathlib `_ - and `dj-database-url `_. - - .. code-block:: python - - # coding: utf-8 - from decouple import config - from unipath import Path - from dj_database_url import parse as db_url - - - BASE_DIR = Path(__file__).parent - - DEBUG = config('DEBUG', default=False, cast=bool) - TEMPLATE_DEBUG = DEBUG - - DATABASES = { - 'default': config( - 'DATABASE_URL', - default='sqlite:///' + BASE_DIR.child('db.sqlite3'), - cast=db_url - ) - } - - TIME_ZONE = 'America/Sao_Paulo' - USE_L10N = True - USE_TZ = True - - SECRET_KEY = config('SECRET_KEY') - - EMAIL_HOST = config('EMAIL_HOST', default='localhost') - EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) - EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='') - EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='') - EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool) - - # ... - - Attention with *undefined* parameters - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - On the above example, all configuration parameters except ``SECRET_KEY = config('SECRET_KEY')`` - have a default value to fallback if it does not exist on the ``.env`` file. - - If ``SECRET_KEY`` is not present on the ``.env``, *decouple* will raise an ``UndefinedValueError``. - - This *fail fast* policy helps you avoid chasing misbehaviors when you eventually forget a parameter. - - Overriding config files with environment variables - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Some times you may want to change a parameter value without having to edit the ``.ini`` or ``.env`` files. - - Since version 3.0, *decouple* respect the *unix way*. - Therefore environment variables have precedence over config files. - - To override a config parameter you can simply do: - - .. code-block:: console - - DEBUG=True python manage.py - - - How it works? - ============= - - *Decouple* always searches for *Options* in this order: - - #. Environment variables; - #. Repository: ini or .env file; - #. default argument passed to config. - - There are 4 classes doing the magic: - - - - ``Config`` - - Coordinates all the configuration retrieval. - - - ``RepositoryIni`` - - Can read values from ``os.environ`` and ini files, in that order. - - **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. - - - ``RepositoryEnv`` - - Can read values from ``os.environ`` and ``.env`` files. - - **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. - - - ``AutoConfig`` - - This is a *lazy* ``Config`` factory that detects which configuration repository you're using. - - It recursively searches up your configuration module path looking for a - ``settings.ini`` or a ``.env`` file. - - Optionally, it accepts ``search_path`` argument to explicitly define - where the search starts. - - The **config** object is an instance of ``AutoConfig`` that instantiates a ``Config`` with the proper ``Repository`` - on the first time it is used. - - - Understanding the CAST argument - ------------------------------- - - By default, all values returned by `decouple` are `strings`, after all they are - read from `text files` or the `envvars`. - - However, your Python code may expect some other value type, for example: - - * Django's DEBUG expects a boolean True or False. - * Django's EMAIL_PORT expects an integer. - * Django's ALLOWED_HOSTS expects a list of hostnames. - * Django's SECURE_PROXY_SSL_HEADER expects a `tuple` with two elements, the name of the header to look for and the required value. - - To meet this need, the `config` function accepts a `cast` argument which - receives any *callable*, that will be used to *transform* the string value - into something else. - - Let's see some examples for the above mentioned cases: - - .. code-block:: pycon - - >>> os.environ['DEBUG'] = 'False' - >>> config('DEBUG', cast=bool) - False - - >>> os.environ['EMAIL_PORT'] = '42' - >>> config('EMAIL_PORT', cast=int) - 42 - - >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' - >>> config('ALLOWED_HOSTS', cast=lambda v: [s.strip() for s in v.split(',')]) - ['.localhost', '.herokuapp.com'] - - >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' - >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(tuple_=True)) - ('HTTP_X_FORWARDED_PROTO', 'https') - - As you can see, `cast` is very flexible. But the last example got a bit complex. - - Built in Csv Helper - ~~~~~~~~~~~~~~~~~~~ - - To address the complexity of the last example, *Decouple* comes with an extensible *Csv helper*. - - Let's improve the last example: - - .. code-block:: pycon - - >>> from decouple import Csv - >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' - >>> config('ALLOWED_HOSTS', cast=Csv()) - ['.localhost', '.herokuapp.com'] - - You can also parametrize the *Csv Helper* to return other types of data. - - .. code-block:: pycon - - >>> os.environ['LIST_OF_INTEGERS'] = '1,2,3,4,5' - >>> config('LIST_OF_INTEGERS', cast=Csv(int)) - [1, 2, 3, 4, 5] - - >>> os.environ['COMPLEX_STRING'] = '%virtual_env%\t *important stuff*\t trailing spaces ' - >>> csv = Csv(cast=lambda s: s.upper(), delimiter='\t', strip=' %*') - >>> csv(os.environ['COMPLEX_STRING']) - ['VIRTUAL_ENV', 'IMPORTANT STUFF', 'TRAILING SPACES'] - - By default *Csv* returns a `list`, but you can get a `tuple` or whatever you want using the `post_process` argument: - - .. code-block:: pycon - - >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' - >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) - ('HTTP_X_FORWARDED_PROTO', 'https') - - - Contribute - ========== - - Your contribution is welcome. - - Setup you development environment: - - .. code-block:: console - - git clone git@github.com:henriquebastos/python-decouple.git - cd python-decouple - python -m venv .venv - source .venv/bin/activate - pip install -r requirements.txt - tox - - *Decouple* supports both Python 2.7 and 3.6. Make sure you have both installed. - - I use `pyenv `_ to - manage multiple Python versions and I described my workspace setup on this article: - `The definitive guide to setup my Python workspace - `_ - - You can submit pull requests and issues for discussion. However I only - consider merge tested code. - - - License - ======= - - The MIT License (MIT) - - Copyright (c) 2017 Henrique Bastos - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django @@ -384,3 +17,443 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries +License-File: LICENSE + +Python Decouple: Strict separation of settings from code +======================================================== + +*Decouple* helps you to organize your settings so that you can +change parameters without having to redeploy your app. + +It also makes it easy for you to: + +#. store parameters in *ini* or *.env* files; +#. define comprehensive default values; +#. properly convert values to the correct data type; +#. have **only one** configuration module to rule all your instances. + +It was originally designed for Django, but became an independent generic tool +for separating settings from code. + +.. image:: https://img.shields.io/travis/henriquebastos/python-decouple.svg + :target: https://travis-ci.org/henriquebastos/python-decouple + :alt: Build Status + +.. image:: https://img.shields.io/pypi/v/python-decouple.svg + :target: https://pypi.python.org/pypi/python-decouple/ + :alt: Latest PyPI version + + + +.. contents:: Summary + + +Why? +==== + +The settings files in web frameworks store many different kinds of parameters: + +* Locale and i18n; +* Middlewares and Installed Apps; +* Resource handles to the database, Memcached, and other backing services; +* Credentials to external services such as Amazon S3 or Twitter; +* Per-deploy values such as the canonical hostname for the instance. + +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? +--------------------------------------- + +*Envvars* works, but since ``os.environ`` only returns strings, it's tricky. + +Let's say you have an *envvar* ``DEBUG=False``. If you run: + +.. code-block:: python + + if os.environ['DEBUG']: + print True + else: + print False + +It will print **True**, because ``os.environ['DEBUG']`` returns the **string** ``"False"``. +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: + +.. code-block:: console + + pip install python-decouple + + +Then use it on your ``settings.py``. + +#. Import the ``config`` object: + + .. code-block:: python + + from decouple import config + +#. Retrieve the configuration parameters: + + .. code-block:: python + + SECRET_KEY = config('SECRET_KEY') + DEBUG = config('DEBUG', default=False, cast=bool) + EMAIL_HOST = config('EMAIL_HOST', default='localhost') + EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) + +Encodings +--------- +Decouple's default encoding is `UTF-8`. + +But you can specify your preferred encoding. + +Since `config` is lazy and only opens the configuration file when it's first needed, you have the chance to change +its encoding right after import. + +.. code-block:: python + + from decouple import config + config.encoding = 'cp1251' + SECRET_KEY = config('SECRET_KEY') + +If you wish to fall back to your system's default encoding use: + +.. code-block:: python + + import locale + from decouple import config + 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 +~~~~~~~~ + +Simply create a ``settings.ini`` next to your configuration module in the form: + +.. code-block:: ini + + [settings] + DEBUG=True + TEMPLATE_DEBUG=%(DEBUG)s + SECRET_KEY=ARANDOMSECRETKEY + DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase + PERCENTILE=90%% + #COMMENTED=42 + +*Note*: Since ``ConfigParser`` supports *string interpolation*, to represent the character ``%`` you need to escape it as ``%%``. + +Env file +~~~~~~~~ + +Simply create a ``.env`` text file in your repository's root directory in the form: + +.. code-block:: console + + DEBUG=True + TEMPLATE_DEBUG=True + SECRET_KEY=ARANDOMSECRETKEY + DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase + PERCENTILE=90% + #COMMENTED=42 + +Example: How do I use it with Django? +------------------------------------- + +Given that I have a ``.env`` file in my repository's root directory, here is a snippet of my ``settings.py``. + +I also recommend using `pathlib `_ +and `dj-database-url `_. + +.. code-block:: python + + # coding: utf-8 + from decouple import config + from unipath import Path + from dj_database_url import parse as db_url + + + BASE_DIR = Path(__file__).parent + + DEBUG = config('DEBUG', default=False, cast=bool) + TEMPLATE_DEBUG = DEBUG + + DATABASES = { + 'default': config( + 'DATABASE_URL', + default='sqlite:///' + BASE_DIR.child('db.sqlite3'), + cast=db_url + ) + } + + TIME_ZONE = 'America/Sao_Paulo' + USE_L10N = True + USE_TZ = True + + SECRET_KEY = config('SECRET_KEY') + + EMAIL_HOST = config('EMAIL_HOST', default='localhost') + EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) + EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='') + EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='') + EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool) + + # ... + +Attention with *undefined* parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the above example, all configuration parameters except ``SECRET_KEY = config('SECRET_KEY')`` +have a default value in case it does not exist in the ``.env`` file. + +If ``SECRET_KEY`` is not present in the ``.env``, *decouple* will raise an ``UndefinedValueError``. + +This *fail fast* policy helps you avoid chasing misbehaviours when you eventually forget a parameter. + +Overriding config files with environment variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you may want to change a parameter value without having to edit the ``.ini`` or ``.env`` files. + +Since version 3.0, *decouple* respects the *unix way*. +Therefore environment variables have precedence over config files. + +To override a config parameter you can simply do: + +.. code-block:: console + + DEBUG=True python manage.py + + +How does it work? +================= + +*Decouple* always searches for *Options* in this order: + +#. Environment variables; +#. Repository: ini or .env file; +#. Default argument passed to config. + +There are 4 classes doing the magic: + + +- ``Config`` + + Coordinates all the configuration retrieval. + +- ``RepositoryIni`` + + Can read values from ``os.environ`` and ini files, in that order. + + **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. + +- ``RepositoryEnv`` + + Can read values from ``os.environ`` and ``.env`` files. + + **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. + +- ``AutoConfig`` + + This is a *lazy* ``Config`` factory that detects which configuration repository you're using. + + It recursively searches up your configuration module path looking for a + ``settings.ini`` or a ``.env`` file. + + Optionally, it accepts ``search_path`` argument to explicitly define + where the search starts. + +The **config** object is an instance of ``AutoConfig`` that instantiates a ``Config`` with the proper ``Repository`` +on the first time it is used. + + +Understanding the CAST argument +------------------------------- + +By default, all values returned by ``decouple`` are ``strings``, after all they are +read from ``text files`` or the ``envvars``. + +However, your Python code may expect some other value type, for example: + +* Django's ``DEBUG`` expects a boolean ``True`` or ``False``. +* Django's ``EMAIL_PORT`` expects an ``integer``. +* Django's ``ALLOWED_HOSTS`` expects a ``list`` of hostnames. +* Django's ``SECURE_PROXY_SSL_HEADER`` expects a ``tuple`` with two elements, the name of the header to look for and the required value. + +To meet this need, the ``config`` function accepts a ``cast`` argument which +receives any *callable*, that will be used to *transform* the string value +into something else. + +Let's see some examples for the above mentioned cases: + +.. code-block:: python + + >>> os.environ['DEBUG'] = 'False' + >>> config('DEBUG', cast=bool) + False + + >>> os.environ['EMAIL_PORT'] = '42' + >>> config('EMAIL_PORT', cast=int) + 42 + + >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' + >>> config('ALLOWED_HOSTS', cast=lambda v: [s.strip() for s in v.split(',')]) + ['.localhost', '.herokuapp.com'] + + >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' + >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) + ('HTTP_X_FORWARDED_PROTO', 'https') + +As you can see, ``cast`` is very flexible. But the last example got a bit complex. + +Built in Csv Helper +~~~~~~~~~~~~~~~~~~~ + +To address the complexity of the last example, *Decouple* comes with an extensible *Csv helper*. + +Let's improve the last example: + +.. code-block:: python + + >>> from decouple import Csv + >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' + >>> config('ALLOWED_HOSTS', cast=Csv()) + ['.localhost', '.herokuapp.com'] + +You can also have a `default` value that must be a string to be processed by `Csv`. + +.. code-block:: python + + >>> from decouple import Csv + >>> config('ALLOWED_HOSTS', default='127.0.0.1', cast=Csv()) + ['127.0.0.1'] + +You can also parametrize the *Csv Helper* to return other types of data. + +.. code-block:: python + + >>> os.environ['LIST_OF_INTEGERS'] = '1,2,3,4,5' + >>> config('LIST_OF_INTEGERS', cast=Csv(int)) + [1, 2, 3, 4, 5] + + >>> os.environ['COMPLEX_STRING'] = '%virtual_env%\t *important stuff*\t trailing spaces ' + >>> csv = Csv(cast=lambda s: s.upper(), delimiter='\t', strip=' %*') + >>> csv(os.environ['COMPLEX_STRING']) + ['VIRTUAL_ENV', 'IMPORTANT STUFF', 'TRAILING SPACES'] + +By default *Csv* returns a ``list``, but you can get a ``tuple`` or whatever you want using the ``post_process`` argument: + +.. code-block:: python + + >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' + >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) + ('HTTP_X_FORWARDED_PROTO', 'https') + +Built in Choices helper +~~~~~~~~~~~~~~~~~~~~~~~ + +Allows for cast and validation based on a list of choices. For example: + +.. code-block:: python + + >>> from decouple import config, Choices + >>> os.environ['CONNECTION_TYPE'] = 'usb' + >>> config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth'])) + 'usb' + + >>> os.environ['CONNECTION_TYPE'] = 'serial' + >>> config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth'])) + Traceback (most recent call last): + ... + ValueError: Value not in list: 'serial'; valid values are ['eth', 'usb', 'bluetooth'] + +You can also parametrize *Choices helper* to cast to another type: + +.. code-block:: python + + >>> os.environ['SOME_NUMBER'] = '42' + >>> config('SOME_NUMBER', cast=Choices([7, 14, 42], cast=int)) + 42 + +You can also use a Django-like choices tuple: + +.. code-block:: python + + >>> USB = 'usb' + >>> ETH = 'eth' + >>> BLUETOOTH = 'bluetooth' + >>> + >>> CONNECTION_OPTIONS = ( + ... (USB, 'USB'), + ... (ETH, 'Ethernet'), + ... (BLUETOOTH, 'Bluetooth'),) + ... + >>> os.environ['CONNECTION_TYPE'] = BLUETOOTH + >>> config('CONNECTION_TYPE', cast=Choices(choices=CONNECTION_OPTIONS)) + 'bluetooth' + +Contribute +========== + +Your contribution is welcome. + +Setup your development environment: + +.. code-block:: console + + git clone git@github.com:henriquebastos/python-decouple.git + cd python-decouple + python -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + tox + +*Decouple* supports both Python 2.7 and 3.6. Make sure you have both installed. + +I use `pyenv `_ to +manage multiple Python versions and I described my workspace setup on this article: +`The definitive guide to setup my Python workspace +`_ + +You can submit pull requests and issues for discussion. However I only +consider merging tested code. + + +License +======= + +The MIT License (MIT) + +Copyright (c) 2017 Henrique Bastos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + diff -Nru python-decouple-3.1/python_decouple.egg-info/PKG-INFO python-decouple-3.6/python_decouple.egg-info/PKG-INFO --- python-decouple-3.1/python_decouple.egg-info/PKG-INFO 2017-08-07 23:11:53.000000000 +0000 +++ python-decouple-3.6/python_decouple.egg-info/PKG-INFO 2022-02-02 19:25:28.000000000 +0000 @@ -1,378 +1,11 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: python-decouple -Version: 3.1 +Version: 3.6 Summary: Strict separation of settings from code. Home-page: http://github.com/henriquebastos/python-decouple/ Author: Henrique Bastos Author-email: henrique@bastos.net License: MIT -Description: Python Decouple: Strict separation of settings from code - ======================================================== - - *Decouple* helps you to organize your settings so that you can - change parameters without having to redeploy your app. - - It also makes easy for you to: - - #. store parameters on *ini* or *.env* files; - #. define comprehensive default values; - #. properly convert values to the correct data type; - #. have **only one** configuration module to rule all your instances. - - It was originally designed for Django, but became an independent generic tool - for separating settings from code. - - .. image:: https://img.shields.io/travis/henriquebastos/python-decouple.svg - :target: https://travis-ci.org/henriquebastos/python-decouple - :alt: Build Status - - .. image:: https://landscape.io/github/henriquebastos/python-decouple/master/landscape.png - :target: https://landscape.io/github/henriquebastos/python-decouple/master - :alt: Code Health - - .. image:: https://img.shields.io/pypi/v/python-decouple.svg - :target: https://pypi.python.org/pypi/python-decouple/ - :alt: Latest PyPI version - - - - .. contents:: Summary - - - Why? - ==== - - Web framework's settings stores many different kinds of parameters: - - * Locale and i18n; - * Middlewares and Installed Apps; - * Resource handles to the database, Memcached, and other backing services; - * Credentials to external services such as Amazon S3 or Twitter; - * Per-deploy values such as the canonical hostname for the instance. - - The first 2 are *project settings* the last 3 are *instance settings*. - - You should be able to change *instance settings* without redeploying your app. - - Why not just use environment variables? - --------------------------------------- - - *Envvars* works, but since ``os.environ`` only returns strings, it's tricky. - - Let's say you have an *envvar* ``DEBUG=False``. If you run: - - .. code-block:: python - - if os.environ['DEBUG']: - print True - else: - print False - - It will print **True**, because ``os.environ['DEBUG']`` returns the **string** ``"False"``. - 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: - - .. code-block:: console - - pip install python-decouple - - - Then use it on your ``settings.py``. - - #. Import the ``config`` object: - - .. code-block:: python - - from decouple import config - - #. Retrieve the configuration parameters: - - .. code-block:: python - - SECRET_KEY = config('SECRET_KEY') - DEBUG = config('DEBUG', default=False, cast=bool) - EMAIL_HOST = config('EMAIL_HOST', default='localhost') - EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) - - Where the settings data are stored? - ----------------------------------- - - *Decouple* supports both *.ini* and *.env* files. - - Ini file - ~~~~~~~~ - - Simply create a ``settings.ini`` next to your configuration module in the form: - - .. code-block:: ini - - [settings] - DEBUG=True - TEMPLATE_DEBUG=%(DEBUG)s - SECRET_KEY=ARANDOMSECRETKEY - DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase - PERCENTILE=90%% - #COMMENTED=42 - - *Note*: Since ``ConfigParser`` supports *string interpolation*, to represent the character ``%`` you need to escape it as ``%%``. - - Env file - ~~~~~~~~ - - Simply create a ``.env`` text file on your repository's root directory in the form: - - .. code-block:: console - - DEBUG=True - TEMPLATE_DEBUG=True - SECRET_KEY=ARANDOMSECRETKEY - DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase - PERCENTILE=90% - #COMMENTED=42 - - Example: How do I use it with Django? - ------------------------------------- - - Given that I have a ``.env`` file at my repository root directory, here is a snippet of my ``settings.py``. - - I also recommend using `pathlib `_ - and `dj-database-url `_. - - .. code-block:: python - - # coding: utf-8 - from decouple import config - from unipath import Path - from dj_database_url import parse as db_url - - - BASE_DIR = Path(__file__).parent - - DEBUG = config('DEBUG', default=False, cast=bool) - TEMPLATE_DEBUG = DEBUG - - DATABASES = { - 'default': config( - 'DATABASE_URL', - default='sqlite:///' + BASE_DIR.child('db.sqlite3'), - cast=db_url - ) - } - - TIME_ZONE = 'America/Sao_Paulo' - USE_L10N = True - USE_TZ = True - - SECRET_KEY = config('SECRET_KEY') - - EMAIL_HOST = config('EMAIL_HOST', default='localhost') - EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) - EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='') - EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='') - EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool) - - # ... - - Attention with *undefined* parameters - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - On the above example, all configuration parameters except ``SECRET_KEY = config('SECRET_KEY')`` - have a default value to fallback if it does not exist on the ``.env`` file. - - If ``SECRET_KEY`` is not present on the ``.env``, *decouple* will raise an ``UndefinedValueError``. - - This *fail fast* policy helps you avoid chasing misbehaviors when you eventually forget a parameter. - - Overriding config files with environment variables - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Some times you may want to change a parameter value without having to edit the ``.ini`` or ``.env`` files. - - Since version 3.0, *decouple* respect the *unix way*. - Therefore environment variables have precedence over config files. - - To override a config parameter you can simply do: - - .. code-block:: console - - DEBUG=True python manage.py - - - How it works? - ============= - - *Decouple* always searches for *Options* in this order: - - #. Environment variables; - #. Repository: ini or .env file; - #. default argument passed to config. - - There are 4 classes doing the magic: - - - - ``Config`` - - Coordinates all the configuration retrieval. - - - ``RepositoryIni`` - - Can read values from ``os.environ`` and ini files, in that order. - - **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. - - - ``RepositoryEnv`` - - Can read values from ``os.environ`` and ``.env`` files. - - **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. - - - ``AutoConfig`` - - This is a *lazy* ``Config`` factory that detects which configuration repository you're using. - - It recursively searches up your configuration module path looking for a - ``settings.ini`` or a ``.env`` file. - - Optionally, it accepts ``search_path`` argument to explicitly define - where the search starts. - - The **config** object is an instance of ``AutoConfig`` that instantiates a ``Config`` with the proper ``Repository`` - on the first time it is used. - - - Understanding the CAST argument - ------------------------------- - - By default, all values returned by `decouple` are `strings`, after all they are - read from `text files` or the `envvars`. - - However, your Python code may expect some other value type, for example: - - * Django's DEBUG expects a boolean True or False. - * Django's EMAIL_PORT expects an integer. - * Django's ALLOWED_HOSTS expects a list of hostnames. - * Django's SECURE_PROXY_SSL_HEADER expects a `tuple` with two elements, the name of the header to look for and the required value. - - To meet this need, the `config` function accepts a `cast` argument which - receives any *callable*, that will be used to *transform* the string value - into something else. - - Let's see some examples for the above mentioned cases: - - .. code-block:: pycon - - >>> os.environ['DEBUG'] = 'False' - >>> config('DEBUG', cast=bool) - False - - >>> os.environ['EMAIL_PORT'] = '42' - >>> config('EMAIL_PORT', cast=int) - 42 - - >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' - >>> config('ALLOWED_HOSTS', cast=lambda v: [s.strip() for s in v.split(',')]) - ['.localhost', '.herokuapp.com'] - - >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' - >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(tuple_=True)) - ('HTTP_X_FORWARDED_PROTO', 'https') - - As you can see, `cast` is very flexible. But the last example got a bit complex. - - Built in Csv Helper - ~~~~~~~~~~~~~~~~~~~ - - To address the complexity of the last example, *Decouple* comes with an extensible *Csv helper*. - - Let's improve the last example: - - .. code-block:: pycon - - >>> from decouple import Csv - >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' - >>> config('ALLOWED_HOSTS', cast=Csv()) - ['.localhost', '.herokuapp.com'] - - You can also parametrize the *Csv Helper* to return other types of data. - - .. code-block:: pycon - - >>> os.environ['LIST_OF_INTEGERS'] = '1,2,3,4,5' - >>> config('LIST_OF_INTEGERS', cast=Csv(int)) - [1, 2, 3, 4, 5] - - >>> os.environ['COMPLEX_STRING'] = '%virtual_env%\t *important stuff*\t trailing spaces ' - >>> csv = Csv(cast=lambda s: s.upper(), delimiter='\t', strip=' %*') - >>> csv(os.environ['COMPLEX_STRING']) - ['VIRTUAL_ENV', 'IMPORTANT STUFF', 'TRAILING SPACES'] - - By default *Csv* returns a `list`, but you can get a `tuple` or whatever you want using the `post_process` argument: - - .. code-block:: pycon - - >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' - >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) - ('HTTP_X_FORWARDED_PROTO', 'https') - - - Contribute - ========== - - Your contribution is welcome. - - Setup you development environment: - - .. code-block:: console - - git clone git@github.com:henriquebastos/python-decouple.git - cd python-decouple - python -m venv .venv - source .venv/bin/activate - pip install -r requirements.txt - tox - - *Decouple* supports both Python 2.7 and 3.6. Make sure you have both installed. - - I use `pyenv `_ to - manage multiple Python versions and I described my workspace setup on this article: - `The definitive guide to setup my Python workspace - `_ - - You can submit pull requests and issues for discussion. However I only - consider merge tested code. - - - License - ======= - - The MIT License (MIT) - - Copyright (c) 2017 Henrique Bastos - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django @@ -384,3 +17,443 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Libraries +License-File: LICENSE + +Python Decouple: Strict separation of settings from code +======================================================== + +*Decouple* helps you to organize your settings so that you can +change parameters without having to redeploy your app. + +It also makes it easy for you to: + +#. store parameters in *ini* or *.env* files; +#. define comprehensive default values; +#. properly convert values to the correct data type; +#. have **only one** configuration module to rule all your instances. + +It was originally designed for Django, but became an independent generic tool +for separating settings from code. + +.. image:: https://img.shields.io/travis/henriquebastos/python-decouple.svg + :target: https://travis-ci.org/henriquebastos/python-decouple + :alt: Build Status + +.. image:: https://img.shields.io/pypi/v/python-decouple.svg + :target: https://pypi.python.org/pypi/python-decouple/ + :alt: Latest PyPI version + + + +.. contents:: Summary + + +Why? +==== + +The settings files in web frameworks store many different kinds of parameters: + +* Locale and i18n; +* Middlewares and Installed Apps; +* Resource handles to the database, Memcached, and other backing services; +* Credentials to external services such as Amazon S3 or Twitter; +* Per-deploy values such as the canonical hostname for the instance. + +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? +--------------------------------------- + +*Envvars* works, but since ``os.environ`` only returns strings, it's tricky. + +Let's say you have an *envvar* ``DEBUG=False``. If you run: + +.. code-block:: python + + if os.environ['DEBUG']: + print True + else: + print False + +It will print **True**, because ``os.environ['DEBUG']`` returns the **string** ``"False"``. +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: + +.. code-block:: console + + pip install python-decouple + + +Then use it on your ``settings.py``. + +#. Import the ``config`` object: + + .. code-block:: python + + from decouple import config + +#. Retrieve the configuration parameters: + + .. code-block:: python + + SECRET_KEY = config('SECRET_KEY') + DEBUG = config('DEBUG', default=False, cast=bool) + EMAIL_HOST = config('EMAIL_HOST', default='localhost') + EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) + +Encodings +--------- +Decouple's default encoding is `UTF-8`. + +But you can specify your preferred encoding. + +Since `config` is lazy and only opens the configuration file when it's first needed, you have the chance to change +its encoding right after import. + +.. code-block:: python + + from decouple import config + config.encoding = 'cp1251' + SECRET_KEY = config('SECRET_KEY') + +If you wish to fall back to your system's default encoding use: + +.. code-block:: python + + import locale + from decouple import config + 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 +~~~~~~~~ + +Simply create a ``settings.ini`` next to your configuration module in the form: + +.. code-block:: ini + + [settings] + DEBUG=True + TEMPLATE_DEBUG=%(DEBUG)s + SECRET_KEY=ARANDOMSECRETKEY + DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase + PERCENTILE=90%% + #COMMENTED=42 + +*Note*: Since ``ConfigParser`` supports *string interpolation*, to represent the character ``%`` you need to escape it as ``%%``. + +Env file +~~~~~~~~ + +Simply create a ``.env`` text file in your repository's root directory in the form: + +.. code-block:: console + + DEBUG=True + TEMPLATE_DEBUG=True + SECRET_KEY=ARANDOMSECRETKEY + DATABASE_URL=mysql://myuser:mypassword@myhost/mydatabase + PERCENTILE=90% + #COMMENTED=42 + +Example: How do I use it with Django? +------------------------------------- + +Given that I have a ``.env`` file in my repository's root directory, here is a snippet of my ``settings.py``. + +I also recommend using `pathlib `_ +and `dj-database-url `_. + +.. code-block:: python + + # coding: utf-8 + from decouple import config + from unipath import Path + from dj_database_url import parse as db_url + + + BASE_DIR = Path(__file__).parent + + DEBUG = config('DEBUG', default=False, cast=bool) + TEMPLATE_DEBUG = DEBUG + + DATABASES = { + 'default': config( + 'DATABASE_URL', + default='sqlite:///' + BASE_DIR.child('db.sqlite3'), + cast=db_url + ) + } + + TIME_ZONE = 'America/Sao_Paulo' + USE_L10N = True + USE_TZ = True + + SECRET_KEY = config('SECRET_KEY') + + EMAIL_HOST = config('EMAIL_HOST', default='localhost') + EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) + EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='') + EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='') + EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=False, cast=bool) + + # ... + +Attention with *undefined* parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the above example, all configuration parameters except ``SECRET_KEY = config('SECRET_KEY')`` +have a default value in case it does not exist in the ``.env`` file. + +If ``SECRET_KEY`` is not present in the ``.env``, *decouple* will raise an ``UndefinedValueError``. + +This *fail fast* policy helps you avoid chasing misbehaviours when you eventually forget a parameter. + +Overriding config files with environment variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you may want to change a parameter value without having to edit the ``.ini`` or ``.env`` files. + +Since version 3.0, *decouple* respects the *unix way*. +Therefore environment variables have precedence over config files. + +To override a config parameter you can simply do: + +.. code-block:: console + + DEBUG=True python manage.py + + +How does it work? +================= + +*Decouple* always searches for *Options* in this order: + +#. Environment variables; +#. Repository: ini or .env file; +#. Default argument passed to config. + +There are 4 classes doing the magic: + + +- ``Config`` + + Coordinates all the configuration retrieval. + +- ``RepositoryIni`` + + Can read values from ``os.environ`` and ini files, in that order. + + **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. + +- ``RepositoryEnv`` + + Can read values from ``os.environ`` and ``.env`` files. + + **Note:** Since version 3.0 *decouple* respects unix precedence of environment variables *over* config files. + +- ``AutoConfig`` + + This is a *lazy* ``Config`` factory that detects which configuration repository you're using. + + It recursively searches up your configuration module path looking for a + ``settings.ini`` or a ``.env`` file. + + Optionally, it accepts ``search_path`` argument to explicitly define + where the search starts. + +The **config** object is an instance of ``AutoConfig`` that instantiates a ``Config`` with the proper ``Repository`` +on the first time it is used. + + +Understanding the CAST argument +------------------------------- + +By default, all values returned by ``decouple`` are ``strings``, after all they are +read from ``text files`` or the ``envvars``. + +However, your Python code may expect some other value type, for example: + +* Django's ``DEBUG`` expects a boolean ``True`` or ``False``. +* Django's ``EMAIL_PORT`` expects an ``integer``. +* Django's ``ALLOWED_HOSTS`` expects a ``list`` of hostnames. +* Django's ``SECURE_PROXY_SSL_HEADER`` expects a ``tuple`` with two elements, the name of the header to look for and the required value. + +To meet this need, the ``config`` function accepts a ``cast`` argument which +receives any *callable*, that will be used to *transform* the string value +into something else. + +Let's see some examples for the above mentioned cases: + +.. code-block:: python + + >>> os.environ['DEBUG'] = 'False' + >>> config('DEBUG', cast=bool) + False + + >>> os.environ['EMAIL_PORT'] = '42' + >>> config('EMAIL_PORT', cast=int) + 42 + + >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' + >>> config('ALLOWED_HOSTS', cast=lambda v: [s.strip() for s in v.split(',')]) + ['.localhost', '.herokuapp.com'] + + >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' + >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) + ('HTTP_X_FORWARDED_PROTO', 'https') + +As you can see, ``cast`` is very flexible. But the last example got a bit complex. + +Built in Csv Helper +~~~~~~~~~~~~~~~~~~~ + +To address the complexity of the last example, *Decouple* comes with an extensible *Csv helper*. + +Let's improve the last example: + +.. code-block:: python + + >>> from decouple import Csv + >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' + >>> config('ALLOWED_HOSTS', cast=Csv()) + ['.localhost', '.herokuapp.com'] + +You can also have a `default` value that must be a string to be processed by `Csv`. + +.. code-block:: python + + >>> from decouple import Csv + >>> config('ALLOWED_HOSTS', default='127.0.0.1', cast=Csv()) + ['127.0.0.1'] + +You can also parametrize the *Csv Helper* to return other types of data. + +.. code-block:: python + + >>> os.environ['LIST_OF_INTEGERS'] = '1,2,3,4,5' + >>> config('LIST_OF_INTEGERS', cast=Csv(int)) + [1, 2, 3, 4, 5] + + >>> os.environ['COMPLEX_STRING'] = '%virtual_env%\t *important stuff*\t trailing spaces ' + >>> csv = Csv(cast=lambda s: s.upper(), delimiter='\t', strip=' %*') + >>> csv(os.environ['COMPLEX_STRING']) + ['VIRTUAL_ENV', 'IMPORTANT STUFF', 'TRAILING SPACES'] + +By default *Csv* returns a ``list``, but you can get a ``tuple`` or whatever you want using the ``post_process`` argument: + +.. code-block:: python + + >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' + >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) + ('HTTP_X_FORWARDED_PROTO', 'https') + +Built in Choices helper +~~~~~~~~~~~~~~~~~~~~~~~ + +Allows for cast and validation based on a list of choices. For example: + +.. code-block:: python + + >>> from decouple import config, Choices + >>> os.environ['CONNECTION_TYPE'] = 'usb' + >>> config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth'])) + 'usb' + + >>> os.environ['CONNECTION_TYPE'] = 'serial' + >>> config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth'])) + Traceback (most recent call last): + ... + ValueError: Value not in list: 'serial'; valid values are ['eth', 'usb', 'bluetooth'] + +You can also parametrize *Choices helper* to cast to another type: + +.. code-block:: python + + >>> os.environ['SOME_NUMBER'] = '42' + >>> config('SOME_NUMBER', cast=Choices([7, 14, 42], cast=int)) + 42 + +You can also use a Django-like choices tuple: + +.. code-block:: python + + >>> USB = 'usb' + >>> ETH = 'eth' + >>> BLUETOOTH = 'bluetooth' + >>> + >>> CONNECTION_OPTIONS = ( + ... (USB, 'USB'), + ... (ETH, 'Ethernet'), + ... (BLUETOOTH, 'Bluetooth'),) + ... + >>> os.environ['CONNECTION_TYPE'] = BLUETOOTH + >>> config('CONNECTION_TYPE', cast=Choices(choices=CONNECTION_OPTIONS)) + 'bluetooth' + +Contribute +========== + +Your contribution is welcome. + +Setup your development environment: + +.. code-block:: console + + git clone git@github.com:henriquebastos/python-decouple.git + cd python-decouple + python -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + tox + +*Decouple* supports both Python 2.7 and 3.6. Make sure you have both installed. + +I use `pyenv `_ to +manage multiple Python versions and I described my workspace setup on this article: +`The definitive guide to setup my Python workspace +`_ + +You can submit pull requests and issues for discussion. However I only +consider merging tested code. + + +License +======= + +The MIT License (MIT) + +Copyright (c) 2017 Henrique Bastos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + diff -Nru python-decouple-3.1/python_decouple.egg-info/SOURCES.txt python-decouple-3.6/python_decouple.egg-info/SOURCES.txt --- python-decouple-3.1/python_decouple.egg-info/SOURCES.txt 2017-08-07 23:11:53.000000000 +0000 +++ python-decouple-3.6/python_decouple.egg-info/SOURCES.txt 2022-02-02 19:25:28.000000000 +0000 @@ -1,5 +1,8 @@ +LICENSE +MANIFEST.in README.rst decouple.py +setup.cfg setup.py python_decouple.egg-info/PKG-INFO python_decouple.egg-info/SOURCES.txt diff -Nru python-decouple-3.1/README.rst python-decouple-3.6/README.rst --- python-decouple-3.1/README.rst 2017-08-07 23:10:14.000000000 +0000 +++ python-decouple-3.6/README.rst 2022-02-02 19:25:13.000000000 +0000 @@ -4,9 +4,9 @@ *Decouple* helps you to organize your settings so that you can change parameters without having to redeploy your app. -It also makes easy for you to: +It also makes it easy for you to: -#. store parameters on *ini* or *.env* files; +#. store parameters in *ini* or *.env* files; #. define comprehensive default values; #. properly convert values to the correct data type; #. have **only one** configuration module to rule all your instances. @@ -18,10 +18,6 @@ :target: https://travis-ci.org/henriquebastos/python-decouple :alt: Build Status -.. image:: https://landscape.io/github/henriquebastos/python-decouple/master/landscape.png - :target: https://landscape.io/github/henriquebastos/python-decouple/master - :alt: Code Health - .. image:: https://img.shields.io/pypi/v/python-decouple.svg :target: https://pypi.python.org/pypi/python-decouple/ :alt: Latest PyPI version @@ -34,7 +30,7 @@ Why? ==== -Web framework's settings stores many different kinds of parameters: +The settings files in web frameworks store many different kinds of parameters: * Locale and i18n; * Middlewares and Installed Apps; @@ -42,7 +38,7 @@ * Credentials to external services such as Amazon S3 or Twitter; * Per-deploy values such as the canonical hostname for the instance. -The first 2 are *project settings* the last 3 are *instance settings*. +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. @@ -92,7 +88,31 @@ EMAIL_HOST = config('EMAIL_HOST', default='localhost') EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) -Where the settings data are stored? +Encodings +--------- +Decouple's default encoding is `UTF-8`. + +But you can specify your preferred encoding. + +Since `config` is lazy and only opens the configuration file when it's first needed, you have the chance to change +its encoding right after import. + +.. code-block:: python + + from decouple import config + config.encoding = 'cp1251' + SECRET_KEY = config('SECRET_KEY') + +If you wish to fall back to your system's default encoding use: + +.. code-block:: python + + import locale + from decouple import config + config.encoding = locale.getpreferredencoding(False) + SECRET_KEY = config('SECRET_KEY') + +Where is the settings data stored? ----------------------------------- *Decouple* supports both *.ini* and *.env* files. @@ -117,7 +137,7 @@ Env file ~~~~~~~~ -Simply create a ``.env`` text file on your repository's root directory in the form: +Simply create a ``.env`` text file in your repository's root directory in the form: .. code-block:: console @@ -131,7 +151,7 @@ Example: How do I use it with Django? ------------------------------------- -Given that I have a ``.env`` file at my repository root directory, here is a snippet of my ``settings.py``. +Given that I have a ``.env`` file in my repository's root directory, here is a snippet of my ``settings.py``. I also recommend using `pathlib `_ and `dj-database-url `_. @@ -174,19 +194,19 @@ Attention with *undefined* parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -On the above example, all configuration parameters except ``SECRET_KEY = config('SECRET_KEY')`` -have a default value to fallback if it does not exist on the ``.env`` file. +In the above example, all configuration parameters except ``SECRET_KEY = config('SECRET_KEY')`` +have a default value in case it does not exist in the ``.env`` file. -If ``SECRET_KEY`` is not present on the ``.env``, *decouple* will raise an ``UndefinedValueError``. +If ``SECRET_KEY`` is not present in the ``.env``, *decouple* will raise an ``UndefinedValueError``. -This *fail fast* policy helps you avoid chasing misbehaviors when you eventually forget a parameter. +This *fail fast* policy helps you avoid chasing misbehaviours when you eventually forget a parameter. Overriding config files with environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Some times you may want to change a parameter value without having to edit the ``.ini`` or ``.env`` files. +Sometimes you may want to change a parameter value without having to edit the ``.ini`` or ``.env`` files. -Since version 3.0, *decouple* respect the *unix way*. +Since version 3.0, *decouple* respects the *unix way*. Therefore environment variables have precedence over config files. To override a config parameter you can simply do: @@ -196,14 +216,14 @@ DEBUG=True python manage.py -How it works? -============= +How does it work? +================= *Decouple* always searches for *Options* in this order: #. Environment variables; #. Repository: ini or .env file; -#. default argument passed to config. +#. Default argument passed to config. There are 4 classes doing the magic: @@ -241,23 +261,23 @@ Understanding the CAST argument ------------------------------- -By default, all values returned by `decouple` are `strings`, after all they are -read from `text files` or the `envvars`. +By default, all values returned by ``decouple`` are ``strings``, after all they are +read from ``text files`` or the ``envvars``. However, your Python code may expect some other value type, for example: -* Django's DEBUG expects a boolean True or False. -* Django's EMAIL_PORT expects an integer. -* Django's ALLOWED_HOSTS expects a list of hostnames. -* Django's SECURE_PROXY_SSL_HEADER expects a `tuple` with two elements, the name of the header to look for and the required value. +* Django's ``DEBUG`` expects a boolean ``True`` or ``False``. +* Django's ``EMAIL_PORT`` expects an ``integer``. +* Django's ``ALLOWED_HOSTS`` expects a ``list`` of hostnames. +* Django's ``SECURE_PROXY_SSL_HEADER`` expects a ``tuple`` with two elements, the name of the header to look for and the required value. -To meet this need, the `config` function accepts a `cast` argument which +To meet this need, the ``config`` function accepts a ``cast`` argument which receives any *callable*, that will be used to *transform* the string value into something else. Let's see some examples for the above mentioned cases: -.. code-block:: pycon +.. code-block:: python >>> os.environ['DEBUG'] = 'False' >>> config('DEBUG', cast=bool) @@ -272,10 +292,10 @@ ['.localhost', '.herokuapp.com'] >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' - >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(tuple_=True)) + >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) ('HTTP_X_FORWARDED_PROTO', 'https') -As you can see, `cast` is very flexible. But the last example got a bit complex. +As you can see, ``cast`` is very flexible. But the last example got a bit complex. Built in Csv Helper ~~~~~~~~~~~~~~~~~~~ @@ -284,16 +304,24 @@ Let's improve the last example: -.. code-block:: pycon +.. code-block:: python >>> from decouple import Csv >>> os.environ['ALLOWED_HOSTS'] = '.localhost, .herokuapp.com' >>> config('ALLOWED_HOSTS', cast=Csv()) ['.localhost', '.herokuapp.com'] +You can also have a `default` value that must be a string to be processed by `Csv`. + +.. code-block:: python + + >>> from decouple import Csv + >>> config('ALLOWED_HOSTS', default='127.0.0.1', cast=Csv()) + ['127.0.0.1'] + You can also parametrize the *Csv Helper* to return other types of data. -.. code-block:: pycon +.. code-block:: python >>> os.environ['LIST_OF_INTEGERS'] = '1,2,3,4,5' >>> config('LIST_OF_INTEGERS', cast=Csv(int)) @@ -304,21 +332,63 @@ >>> csv(os.environ['COMPLEX_STRING']) ['VIRTUAL_ENV', 'IMPORTANT STUFF', 'TRAILING SPACES'] -By default *Csv* returns a `list`, but you can get a `tuple` or whatever you want using the `post_process` argument: +By default *Csv* returns a ``list``, but you can get a ``tuple`` or whatever you want using the ``post_process`` argument: -.. code-block:: pycon +.. code-block:: python >>> os.environ['SECURE_PROXY_SSL_HEADER'] = 'HTTP_X_FORWARDED_PROTO, https' >>> config('SECURE_PROXY_SSL_HEADER', cast=Csv(post_process=tuple)) ('HTTP_X_FORWARDED_PROTO', 'https') +Built in Choices helper +~~~~~~~~~~~~~~~~~~~~~~~ + +Allows for cast and validation based on a list of choices. For example: + +.. code-block:: python + + >>> from decouple import config, Choices + >>> os.environ['CONNECTION_TYPE'] = 'usb' + >>> config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth'])) + 'usb' + + >>> os.environ['CONNECTION_TYPE'] = 'serial' + >>> config('CONNECTION_TYPE', cast=Choices(['eth', 'usb', 'bluetooth'])) + Traceback (most recent call last): + ... + ValueError: Value not in list: 'serial'; valid values are ['eth', 'usb', 'bluetooth'] + +You can also parametrize *Choices helper* to cast to another type: + +.. code-block:: python + + >>> os.environ['SOME_NUMBER'] = '42' + >>> config('SOME_NUMBER', cast=Choices([7, 14, 42], cast=int)) + 42 + +You can also use a Django-like choices tuple: + +.. code-block:: python + + >>> USB = 'usb' + >>> ETH = 'eth' + >>> BLUETOOTH = 'bluetooth' + >>> + >>> CONNECTION_OPTIONS = ( + ... (USB, 'USB'), + ... (ETH, 'Ethernet'), + ... (BLUETOOTH, 'Bluetooth'),) + ... + >>> os.environ['CONNECTION_TYPE'] = BLUETOOTH + >>> config('CONNECTION_TYPE', cast=Choices(choices=CONNECTION_OPTIONS)) + 'bluetooth' Contribute ========== Your contribution is welcome. -Setup you development environment: +Setup your development environment: .. code-block:: console @@ -337,7 +407,7 @@ `_ You can submit pull requests and issues for discussion. However I only -consider merge tested code. +consider merging tested code. License diff -Nru python-decouple-3.1/setup.cfg python-decouple-3.6/setup.cfg --- python-decouple-3.1/setup.cfg 2017-08-07 23:11:53.000000000 +0000 +++ python-decouple-3.6/setup.cfg 2022-02-02 19:25:28.534947200 +0000 @@ -1,3 +1,6 @@ +[metadata] +license-file = LICENSE + [egg_info] tag_build = tag_date = 0 diff -Nru python-decouple-3.1/setup.py python-decouple-3.6/setup.py --- python-decouple-3.1/setup.py 2017-08-07 23:09:02.000000000 +0000 +++ python-decouple-3.6/setup.py 2022-02-02 19:25:13.000000000 +0000 @@ -6,7 +6,7 @@ README = os.path.join(os.path.dirname(__file__), 'README.rst') setup(name='python-decouple', - version='3.1', + version='3.6', description='Strict separation of settings from code.', long_description=open(README).read(), author="Henrique Bastos", author_email="henrique@bastos.net",