main.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # -*- coding: utf-8 -*-
  2. from __future__ import absolute_import
  3. import codecs
  4. import os
  5. import sys
  6. import warnings
  7. import re
  8. from collections import OrderedDict
  9. __escape_decoder = codecs.getdecoder('unicode_escape')
  10. __posix_variable = re.compile('\$\{[^\}]*\}')
  11. def decode_escaped(escaped):
  12. return __escape_decoder(escaped)[0]
  13. def get_key(dotenv_path, key_to_get):
  14. """
  15. Gets the value of a given key from the given .env
  16. If the .env path given doesn't exist, fails
  17. """
  18. key_to_get = str(key_to_get)
  19. if not os.path.exists(dotenv_path):
  20. warnings.warn("can't read %s - it doesn't exist." % dotenv_path)
  21. return None
  22. dotenv_as_dict = dotenv_values(dotenv_path)
  23. if key_to_get in dotenv_as_dict:
  24. return dotenv_as_dict[key_to_get]
  25. else:
  26. warnings.warn("key %s not found in %s." % (key_to_get, dotenv_path))
  27. return None
  28. def load_dotenv(dotenv_path, verbose=False, override=False):
  29. """
  30. Read a .env file and load into os.environ.
  31. """
  32. if not os.path.exists(dotenv_path):
  33. if verbose:
  34. warnings.warn("Not loading %s - it doesn't exist." % dotenv_path)
  35. return None
  36. for k, v in dotenv_values(dotenv_path).items():
  37. if override:
  38. os.environ[k] = v
  39. else:
  40. os.environ.setdefault(k, v)
  41. return True
  42. def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
  43. """
  44. Adds or Updates a key/value to the given .env
  45. If the .env path given doesn't exist, fails instead of risking creating
  46. an orphan .env somewhere in the filesystem
  47. """
  48. key_to_set = str(key_to_set)
  49. value_to_set = str(value_to_set).strip("'").strip('"')
  50. if not os.path.exists(dotenv_path):
  51. warnings.warn("can't write to %s - it doesn't exist." % dotenv_path)
  52. return None, key_to_set, value_to_set
  53. dotenv_as_dict = OrderedDict(parse_dotenv(dotenv_path))
  54. dotenv_as_dict[key_to_set] = value_to_set
  55. success = flatten_and_write(dotenv_path, dotenv_as_dict, quote_mode)
  56. return success, key_to_set, value_to_set
  57. def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
  58. """
  59. Removes a given key from the given .env
  60. If the .env path given doesn't exist, fails
  61. If the given key doesn't exist in the .env, fails
  62. """
  63. key_to_unset = str(key_to_unset)
  64. if not os.path.exists(dotenv_path):
  65. warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path)
  66. return None, key_to_unset
  67. dotenv_as_dict = dotenv_values(dotenv_path)
  68. if key_to_unset in dotenv_as_dict:
  69. dotenv_as_dict.pop(key_to_unset, None)
  70. else:
  71. warnings.warn("key %s not removed from %s - key doesn't exist." % (key_to_unset, dotenv_path))
  72. return None, key_to_unset
  73. success = flatten_and_write(dotenv_path, dotenv_as_dict, quote_mode)
  74. return success, key_to_unset
  75. def dotenv_values(dotenv_path):
  76. values = OrderedDict(parse_dotenv(dotenv_path))
  77. values = resolve_nested_variables(values)
  78. return values
  79. def parse_dotenv(dotenv_path):
  80. with open(dotenv_path) as f:
  81. for line in f:
  82. line = line.strip()
  83. if not line or line.startswith('#') or '=' not in line:
  84. continue
  85. k, v = line.split('=', 1)
  86. # Remove any leading and trailing spaces in key, value
  87. k, v = k.strip(), v.strip().encode('unicode-escape').decode('ascii')
  88. if len(v) > 0:
  89. quoted = v[0] == v[len(v) - 1] in ['"', "'"]
  90. if quoted:
  91. v = decode_escaped(v[1:-1])
  92. yield k, v
  93. def resolve_nested_variables(values):
  94. def _replacement(name):
  95. """
  96. get appropriate value for a variable name.
  97. first search in environ, if not found,
  98. then look into the dotenv variables
  99. """
  100. ret = os.getenv(name, values.get(name, ""))
  101. return ret
  102. def _re_sub_callback(match_object):
  103. """
  104. From a match object gets the variable name and returns
  105. the correct replacement
  106. """
  107. return _replacement(match_object.group()[2:-1])
  108. for k, v in values.items():
  109. values[k] = __posix_variable.sub(_re_sub_callback, v)
  110. return values
  111. def flatten_and_write(dotenv_path, dotenv_as_dict, quote_mode="always"):
  112. with open(dotenv_path, "w") as f:
  113. for k, v in dotenv_as_dict.items():
  114. _mode = quote_mode
  115. if _mode == "auto" and " " in v:
  116. _mode = "always"
  117. str_format = '%s="%s"\n' if _mode == "always" else '%s=%s\n'
  118. f.write(str_format % (k, v))
  119. return True
  120. def _walk_to_root(path):
  121. """
  122. Yield directories starting from the given directory up to the root
  123. """
  124. if not os.path.exists(path):
  125. raise IOError('Starting path not found')
  126. if os.path.isfile(path):
  127. path = os.path.dirname(path)
  128. last_dir = None
  129. current_dir = os.path.abspath(path)
  130. while last_dir != current_dir:
  131. yield current_dir
  132. parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
  133. last_dir, current_dir = current_dir, parent_dir
  134. def find_dotenv(filename='.env', raise_error_if_not_found=False, usecwd=False):
  135. """
  136. Search in increasingly higher folders for the given file
  137. Returns path to the file if found, or an empty string otherwise
  138. """
  139. if usecwd or '__file__' not in globals():
  140. # should work without __file__, e.g. in REPL or IPython notebook
  141. path = os.getcwd()
  142. else:
  143. # will work for .py files
  144. frame_filename = sys._getframe().f_back.f_code.co_filename
  145. path = os.path.dirname(os.path.abspath(frame_filename))
  146. for dirname in _walk_to_root(path):
  147. check_path = os.path.join(dirname, filename)
  148. if os.path.exists(check_path):
  149. return check_path
  150. if raise_error_if_not_found:
  151. raise IOError('File not found')
  152. return ''