Skip to content
This repository has been archived by the owner on May 4, 2023. It is now read-only.

Commit

Permalink
Resolve environment variable values
Browse files Browse the repository at this point in the history
Fix joke2k#421: add support for embedded variables.
  • Loading branch information
invasy committed Jan 8, 2023
1 parent 7720a49 commit 5f651a2
Showing 1 changed file with 37 additions and 12 deletions.
49 changes: 37 additions & 12 deletions environ/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import os
import re
import sys
import threading
import urllib.parse as urlparselib
import warnings
from os.path import expandvars
from urllib.parse import (
parse_qs,
ParseResult,
Expand Down Expand Up @@ -187,7 +189,10 @@ class Env:
}
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 @@ -358,9 +363,12 @@ def path(self, var, default=NOTSET, **kwargs):
"""
return Path(self.get_value(var, default=default), **kwargs)

def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
def get_value(self, var, cast=None, default=NOTSET, parse_default=False, add_prefix=True):
"""Return value for given environment variable.
- Substitute environment variable values.
- Detect infinite recursion in values (self-reference).
:param str var:
Name of variable.
:param collections.abc.Callable or None cast:
Expand All @@ -369,15 +377,30 @@ def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
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 = "{}{}".format(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 = "Environment variable '{}' recursively references itself (eventually)".format(var_name)
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 '{}' casted as '{}' with default '{}'".format(
var, cast, default
var_name, cast, default
))

var_name = "{}{}".format(self.prefix, var)
if var_name in self.scheme:
var_info = self.scheme[var_name]

Expand All @@ -403,26 +426,28 @@ def get_value(self, var, cast=None, default=NOTSET, parse_default=False):
value = self.ENVIRON[var_name]
except KeyError as exc:
if default is self.NOTSET:
error_msg = "Set the {} environment variable".format(var)
error_msg = "Set the {} environment variable".format(var_name)
raise ImproperlyConfigured(error_msg) from exc

value = default

# Substitute environment variables
if isinstance(value, (str, bytes)) and var_name != 'DJANGO_SECRET_KEY':
def repl(m):
return self.get_value(m['name'], cast=cast, default=default,
parse_default=parse_default, add_prefix=False)
value = self.VAR.sub(repl, value)
value = expandvars(value)

# 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:
if cast is None and default is not None and \
not isinstance(default, NoValue):
cast = type(default)
if self.smart_cast and 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

0 comments on commit 5f651a2

Please sign in to comment.