123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- import os
- import re
- import sys
- import warnings
- __version__ = '1.4.2'
- line_re = re.compile(r"""
- ^
- (?:export\s+)? # optional export
- ([\w\.]+) # key
- (?:\s*=\s*|:\s+?) # separator
- ( # optional value begin
- '(?:\'|[^'])*' # single quoted value
- | # or
- "(?:\"|[^"])*" # double quoted value
- | # or
- [^#\n]+ # unquoted value
- )? # value end
- (?:\s*\#.*)? # optional comment
- $
- """, re.VERBOSE)
- variable_re = re.compile(r"""
- (\\)? # is it escaped with a backslash?
- (\$) # literal $
- ( # collect braces with var for sub
- \{? # allow brace wrapping
- ([A-Z0-9_]+) # match the variable
- \}? # closing brace
- ) # braces end
- """, re.IGNORECASE | re.VERBOSE)
- def read_dotenv(dotenv=None, override=False):
- """
- Read a .env file into os.environ.
- If not given a path to a dotenv path, does filthy magic stack backtracking
- to find manage.py and then find the dotenv.
- If tests rely on .env files, setting the overwrite flag to True is a safe
- way to ensure tests run consistently across all environments.
- :param override: True if values in .env should override system variables.
- """
- if dotenv is None:
- frame_filename = sys._getframe().f_back.f_code.co_filename
- dotenv = os.path.join(os.path.dirname(frame_filename), '.env')
- if os.path.isdir(dotenv) and os.path.isfile(os.path.join(dotenv, '.env')):
- dotenv = os.path.join(dotenv, '.env')
- if os.path.exists(dotenv):
- with open(dotenv) as f:
- for k, v in parse_dotenv(f.read()).items():
- if override:
- os.environ[k] = v
- else:
- os.environ.setdefault(k, v)
- else:
- warnings.warn("Not reading {0} - it doesn't exist.".format(dotenv),
- stacklevel=2)
- def parse_dotenv(content):
- env = {}
- for line in content.splitlines():
- m1 = line_re.search(line)
- if m1:
- key, value = m1.groups()
- if value is None:
- value = ''
- # Remove leading/trailing whitespace
- value = value.strip()
- # Remove surrounding quotes
- m2 = re.match(r'^([\'"])(.*)\1$', value)
- if m2:
- quotemark, value = m2.groups()
- else:
- quotemark = None
- # Unescape all chars except $ so variables can be escaped properly
- if quotemark == '"':
- value = re.sub(r'\\([^$])', r'\1', value)
- if quotemark != "'":
- # Substitute variables in a value
- for parts in variable_re.findall(value):
- if parts[0] == '\\':
- # Variable is escaped, don't replace it
- replace = ''.join(parts[1:-1])
- else:
- # Replace it with the value from the environment
- replace = env.get(
- parts[-1],
- os.environ.get(parts[-1], '')
- )
- value = value.replace(''.join(parts[0:-1]), replace)
- env[key] = value
- elif not re.search(r'^\s*(?:#.*)?$', line): # not comment or blank
- warnings.warn(
- "Line {0} doesn't match format".format(repr(line)),
- SyntaxWarning
- )
- return env
|