Skip to content

Commit

Permalink
Revert "Add variable expansion (fix #421)"
Browse files Browse the repository at this point in the history
This reverts commit c61bfb0.
  • Loading branch information
sergeyklay committed Sep 1, 2023
1 parent 54daf88 commit b343e05
Show file tree
Hide file tree
Showing 5 changed files with 17 additions and 114 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0

`v0.12.0`_ - 00-Unreleased-2023
-------------------------------

Fixed
+++++
- Revert "Add variable expansion." feature
due to `#490 <https://github.com/joke2k/django-environ/issues/490>`_.


`v0.11.1`_ - 30-August-2023
Expand Down Expand Up @@ -402,4 +405,4 @@ Added
.. _v0.4.1: https://github.com/joke2k/django-environ/compare/v0.4...v0.4.1
.. _v0.4: https://github.com/joke2k/django-environ/compare/v0.3.1...v0.4
.. _v0.3.1: https://github.com/joke2k/django-environ/compare/v0.3...v0.3.1
.. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3
.. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3
22 changes: 0 additions & 22 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,6 @@ And use it with ``settings.py`` as follows:
:start-after: -code-begin-
:end-before: -overview-

Variables can contain references to another variables: ``$VAR`` or ``${VAR}``.
Referenced variables are searched in the environment and within all definitions
in the ``.env`` file. References are checked for recursion (self-reference).
Exception is thrown if any reference results in infinite loop on any level
of recursion. Variable values are substituted similar to shell parameter
expansion. Example:

.. code-block:: shell
# shell
export POSTGRES_USERNAME='user' POSTGRES_PASSWORD='SECRET'
.. code-block:: shell
# .env
POSTGRES_HOSTNAME='example.com'
POSTGRES_DB='database'
DATABASE_URL="postgres://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@${POSTGRES_HOSTNAME}:5432/${POSTGRES_DB}"
The value of ``DATABASE_URL`` variable will become
``postgres://user:[email protected]:5432/database``.

The ``.env`` file should be specific to the environment and not checked into
version control, it is best practice documenting the ``.env`` file with an example.
For example, you can also add ``.env.dist`` with a template of your variables to
Expand Down
66 changes: 12 additions & 54 deletions environ/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
import os
import re
import sys
import threading
import warnings
from os.path import expandvars
from urllib.parse import (
parse_qs,
ParseResult,
Expand All @@ -42,9 +40,6 @@
Openable = (str, os.PathLike)
logger = logging.getLogger(__name__)

# Variables which values should not be expanded
NOT_EXPANDED = 'DJANGO_SECRET_KEY', 'CACHE_URL'


def _cast(value):
# Safely evaluate an expression node or a string containing a Python
Expand Down Expand Up @@ -194,11 +189,7 @@ class Env:
for s in ('', 's')]
CLOUDSQL = 'cloudsql'

VAR = re.compile(r'(?<!\\)\$\{?(?P<name>[A-Z_][0-9A-Z_]*)}?',
re.IGNORECASE)

def __init__(self, **scheme):
self._local = threading.local()
self.smart_cast = True
self.escape_proxy = False
self.prefix = ""
Expand Down Expand Up @@ -352,13 +343,9 @@ def path(self, var, default=NOTSET, **kwargs):
"""
return Path(self.get_value(var, default=default), **kwargs)

def get_value(self, var, cast=None, # pylint: disable=R0913
default=NOTSET, parse_default=False, add_prefix=True):
def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
"""Return value for given environment variable.
- Expand variables referenced as ``$VAR`` or ``${VAR}``.
- Detect infinite recursion in expansion (self-reference).
:param str var:
Name of variable.
:param collections.abc.Callable or None cast:
Expand All @@ -367,33 +354,15 @@ def get_value(self, var, cast=None, # pylint: disable=R0913
If var not present in environ, return this instead.
:param bool parse_default:
Force to parse default.
:param bool add_prefix:
Whether to add prefix to variable name.
:returns: Value from environment or default (if set).
:rtype: typing.IO[typing.Any]
"""
var_name = f'{self.prefix}{var}' if add_prefix else var
if not hasattr(self._local, 'vars'):
self._local.vars = set()
if var_name in self._local.vars:
error_msg = f"Environment variable '{var_name}' recursively "\
"references itself (eventually)"
raise ImproperlyConfigured(error_msg)

self._local.vars.add(var_name)
try:
return self._get_value(
var_name, cast=cast, default=default,
parse_default=parse_default)
finally:
self._local.vars.remove(var_name)

def _get_value(self, var_name, cast=None, default=NOTSET,
parse_default=False):

logger.debug(
"get '%s' casted as '%s' with default '%s'",
var_name, cast, default)
var, cast, default)

var_name = f'{self.prefix}{var}'
if var_name in self.scheme:
var_info = self.scheme[var_name]

Expand All @@ -419,37 +388,26 @@ def _get_value(self, var_name, cast=None, default=NOTSET,
value = self.ENVIRON[var_name]
except KeyError as exc:
if default is self.NOTSET:
error_msg = f'Set the {var_name} environment variable'
error_msg = f'Set the {var} environment variable'
raise ImproperlyConfigured(error_msg) from exc

value = default

# Expand variables
if isinstance(value, (bytes, str)) and var_name not in NOT_EXPANDED:
def repl(match_):
return self.get_value(
match_.group('name'), cast=cast, default=default,
parse_default=parse_default, add_prefix=False)

is_bytes = isinstance(value, bytes)
if is_bytes:
value = value.decode('utf-8')
value = self.VAR.sub(repl, value)
value = expandvars(value)
if is_bytes:
value = value.encode('utf-8')

# Resolve any proxied values
prefix = b'$' if isinstance(value, bytes) else '$'
escape = rb'\$' if isinstance(value, bytes) else r'\$'
if hasattr(value, 'startswith') and value.startswith(prefix):
value = value.lstrip(prefix)
value = self.get_value(value, cast=cast, default=default)

if self.escape_proxy and hasattr(value, 'replace'):
value = value.replace(escape, prefix)

# Smart casting
if self.smart_cast and cast is None and default is not None \
and not isinstance(default, NoValue):
cast = type(default)
if self.smart_cast:
if cast is None and default is not None and \
not isinstance(default, NoValue):
cast = type(default)

value = None if default is None and value == '' else value

Expand Down
27 changes: 0 additions & 27 deletions tests/test_expansion.py

This file was deleted.

9 changes: 0 additions & 9 deletions tests/test_expansion.txt

This file was deleted.

0 comments on commit b343e05

Please sign in to comment.