environment.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import logging
  2. import os
  3. import re
  4. import dotenv
  5. from ..const import IS_WINDOWS_PLATFORM
  6. from .errors import ConfigurationError
  7. from .errors import EnvFileNotFound
  8. log = logging.getLogger(__name__)
  9. def split_env(env):
  10. if isinstance(env, bytes):
  11. env = env.decode('utf-8', 'replace')
  12. key = value = None
  13. if '=' in env:
  14. key, value = env.split('=', 1)
  15. else:
  16. key = env
  17. if re.search(r'\s', key):
  18. raise ConfigurationError(
  19. "environment variable name '{}' may not contain whitespace.".format(key)
  20. )
  21. return key, value
  22. def env_vars_from_file(filename, interpolate=True):
  23. """
  24. Read in a line delimited file of environment variables.
  25. """
  26. if not os.path.exists(filename):
  27. raise EnvFileNotFound("Couldn't find env file: {}".format(filename))
  28. elif not os.path.isfile(filename):
  29. raise EnvFileNotFound("{} is not a file.".format(filename))
  30. env = dotenv.dotenv_values(dotenv_path=filename, encoding='utf-8-sig', interpolate=interpolate)
  31. for k, v in env.items():
  32. env[k] = v if interpolate else v.replace('$', '$$')
  33. return env
  34. class Environment(dict):
  35. def __init__(self, *args, **kwargs):
  36. super().__init__(*args, **kwargs)
  37. self.missing_keys = []
  38. self.silent = False
  39. @classmethod
  40. def from_env_file(cls, base_dir, env_file=None):
  41. def _initialize():
  42. result = cls()
  43. if base_dir is None:
  44. return result
  45. if env_file:
  46. env_file_path = os.path.join(os.getcwd(), env_file)
  47. return cls(env_vars_from_file(env_file_path))
  48. env_file_path = os.path.join(base_dir, '.env')
  49. try:
  50. return cls(env_vars_from_file(env_file_path))
  51. except EnvFileNotFound:
  52. pass
  53. return result
  54. instance = _initialize()
  55. instance.update(os.environ)
  56. return instance
  57. @classmethod
  58. def from_command_line(cls, parsed_env_opts):
  59. result = cls()
  60. for k, v in parsed_env_opts.items():
  61. # Values from the command line take priority, unless they're unset
  62. # in which case they take the value from the system's environment
  63. if v is None and k in os.environ:
  64. result[k] = os.environ[k]
  65. else:
  66. result[k] = v
  67. return result
  68. def __getitem__(self, key):
  69. try:
  70. return super().__getitem__(key)
  71. except KeyError:
  72. if IS_WINDOWS_PLATFORM:
  73. try:
  74. return super().__getitem__(key.upper())
  75. except KeyError:
  76. pass
  77. if not self.silent and key not in self.missing_keys:
  78. log.warning(
  79. "The {} variable is not set. Defaulting to a blank string."
  80. .format(key)
  81. )
  82. self.missing_keys.append(key)
  83. return ""
  84. def __contains__(self, key):
  85. result = super().__contains__(key)
  86. if IS_WINDOWS_PLATFORM:
  87. return (
  88. result or super().__contains__(key.upper())
  89. )
  90. return result
  91. def get(self, key, *args, **kwargs):
  92. if IS_WINDOWS_PLATFORM:
  93. return super().get(
  94. key,
  95. super().get(key.upper(), *args, **kwargs)
  96. )
  97. return super().get(key, *args, **kwargs)
  98. def get_boolean(self, key, default=False):
  99. # Convert a value to a boolean using "common sense" rules.
  100. # Unset, empty, "0" and "false" (i-case) yield False.
  101. # All other values yield True.
  102. value = self.get(key)
  103. if not value:
  104. return default
  105. if value.lower() in ['0', 'false']:
  106. return False
  107. return True