dotenv.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import os
  2. import re
  3. import sys
  4. import warnings
  5. __version__ = '1.4.2'
  6. line_re = re.compile(r"""
  7. ^
  8. (?:export\s+)? # optional export
  9. ([\w\.]+) # key
  10. (?:\s*=\s*|:\s+?) # separator
  11. ( # optional value begin
  12. '(?:\'|[^'])*' # single quoted value
  13. | # or
  14. "(?:\"|[^"])*" # double quoted value
  15. | # or
  16. [^#\n]+ # unquoted value
  17. )? # value end
  18. (?:\s*\#.*)? # optional comment
  19. $
  20. """, re.VERBOSE)
  21. variable_re = re.compile(r"""
  22. (\\)? # is it escaped with a backslash?
  23. (\$) # literal $
  24. ( # collect braces with var for sub
  25. \{? # allow brace wrapping
  26. ([A-Z0-9_]+) # match the variable
  27. \}? # closing brace
  28. ) # braces end
  29. """, re.IGNORECASE | re.VERBOSE)
  30. def read_dotenv(dotenv=None, override=False):
  31. """
  32. Read a .env file into os.environ.
  33. If not given a path to a dotenv path, does filthy magic stack backtracking
  34. to find manage.py and then find the dotenv.
  35. If tests rely on .env files, setting the overwrite flag to True is a safe
  36. way to ensure tests run consistently across all environments.
  37. :param override: True if values in .env should override system variables.
  38. """
  39. if dotenv is None:
  40. frame_filename = sys._getframe().f_back.f_code.co_filename
  41. dotenv = os.path.join(os.path.dirname(frame_filename), '.env')
  42. if os.path.isdir(dotenv) and os.path.isfile(os.path.join(dotenv, '.env')):
  43. dotenv = os.path.join(dotenv, '.env')
  44. if os.path.exists(dotenv):
  45. with open(dotenv) as f:
  46. for k, v in parse_dotenv(f.read()).items():
  47. if override:
  48. os.environ[k] = v
  49. else:
  50. os.environ.setdefault(k, v)
  51. else:
  52. warnings.warn("Not reading {0} - it doesn't exist.".format(dotenv),
  53. stacklevel=2)
  54. def parse_dotenv(content):
  55. env = {}
  56. for line in content.splitlines():
  57. m1 = line_re.search(line)
  58. if m1:
  59. key, value = m1.groups()
  60. if value is None:
  61. value = ''
  62. # Remove leading/trailing whitespace
  63. value = value.strip()
  64. # Remove surrounding quotes
  65. m2 = re.match(r'^([\'"])(.*)\1$', value)
  66. if m2:
  67. quotemark, value = m2.groups()
  68. else:
  69. quotemark = None
  70. # Unescape all chars except $ so variables can be escaped properly
  71. if quotemark == '"':
  72. value = re.sub(r'\\([^$])', r'\1', value)
  73. if quotemark != "'":
  74. # Substitute variables in a value
  75. for parts in variable_re.findall(value):
  76. if parts[0] == '\\':
  77. # Variable is escaped, don't replace it
  78. replace = ''.join(parts[1:-1])
  79. else:
  80. # Replace it with the value from the environment
  81. replace = env.get(
  82. parts[-1],
  83. os.environ.get(parts[-1], '')
  84. )
  85. value = value.replace(''.join(parts[0:-1]), replace)
  86. env[key] = value
  87. elif not re.search(r'^\s*(?:#.*)?$', line): # not comment or blank
  88. warnings.warn(
  89. "Line {0} doesn't match format".format(repr(line)),
  90. SyntaxWarning
  91. )
  92. return env