SHARE
TWEET

Untitled

a guest Oct 20th, 2019 76 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. """Envparse is a simple utility to parse environment variables.
  2. NOTE: can't remember where i got this code. slightly modified.
  3. """
  4. import inspect
  5. import json as pyjson
  6. import logging
  7. import os
  8. import re
  9. import shlex
  10. import warnings
  11. import urllib.parse as urlparse
  12.  
  13.  
  14. logger = logging.getLogger(__name__)
  15.  
  16.  
  17. class ConfigurationError(Exception):
  18.     pass
  19.  
  20.  
  21. # Cannot rely on None since it may be desired as a return value.
  22. NOTSET = type(str('NoValue'), (object,), {})
  23.  
  24.  
  25. def shortcut(cast):
  26.     def method(self, var, **kwargs):
  27.         return self.__call__(var, cast=cast, **kwargs)
  28.     return method
  29.  
  30.  
  31. class Env(object):
  32.     """
  33.     Lookup and cast environment variables with optional schema.
  34.  
  35.     Usage:::
  36.  
  37.         env = Env()
  38.         env('foo')
  39.         env.bool('bar')
  40.  
  41.         # Create env with a schema
  42.         env = Env(MAIL_ENABLED=bool, SMTP_LOGIN=(str, 'DEFAULT'))
  43.         if env('MAIL_ENABLED'):
  44.             ...
  45.     """
  46.     BOOLEAN_TRUE_STRINGS = ('true', 'on', 'ok', 'y', 'yes', '1')
  47.  
  48.     def __init__(self, **schema):
  49.         self.schema = schema
  50.  
  51.     def __call__(self, var, default=NOTSET, cast=None, subcast=None,  # noqa: C901
  52.                  force=False, preprocessor=None, postprocessor=None):
  53.         """
  54.         Return value for given environment variable.
  55.  
  56.         :param var: Name of variable.
  57.         :param default: If var not present in environ, return this instead.
  58.         :param cast: Type or callable to cast return value as.
  59.         :param subcast: Subtype or callable to cast return values as (used for
  60.                         nested structures).
  61.         :param force: force to cast to type even if default is set.
  62.         :param preprocessor: callable to run on pre-casted value.
  63.         :param postprocessor: callable to run on casted value.
  64.  
  65.         :returns: Value from environment or default (if set).
  66.         """
  67.         logger.debug("Get '%s' casted as '%s'/'%s' with default '%s'", var,
  68.                      cast, subcast, default)
  69.  
  70.         if var in self.schema:
  71.             params = self.schema[var]
  72.             if isinstance(params, dict):
  73.                 if cast is None:
  74.                     cast = params.get('cast', cast)
  75.                 if subcast is None:
  76.                     subcast = params.get('subcast', subcast)
  77.                 if default == NOTSET:
  78.                     default = params.get('default', default)
  79.             else:
  80.                 if cast is None:
  81.                     cast = params
  82.  
  83.         # Default cast is `str` if it is not specified. Most types will be
  84.         # implicitly strings so reduces having to specify.
  85.         cast = str if cast is None else cast
  86.  
  87.         try:
  88.             value = os.environ[var]
  89.         except KeyError:
  90.             if default is NOTSET:
  91.                 error_msg = "Environment variable '{}' not set.".format(var)
  92.                 raise ConfigurationError(error_msg)
  93.             else:
  94.                 value = default
  95.  
  96.         # Resolve any proxied values
  97.         if hasattr(value, 'startswith') and value.startswith('{{'):
  98.             value = self.__call__(value.lstrip('{{}}'), default, cast, subcast,
  99.                                   default, force, preprocessor, postprocessor)
  100.  
  101.         if preprocessor:
  102.             value = preprocessor(value)
  103.         if value != default or force:
  104.             value = self.cast(value, cast, subcast)
  105.         if postprocessor:
  106.             value = postprocessor(value)
  107.         return value
  108.  
  109.     @classmethod
  110.     def cast(cls, value, cast=str, subcast=None):
  111.         """
  112.         Parse and cast provided value.
  113.  
  114.         :param value: Stringed value.
  115.         :param cast: Type or callable to cast return value as.
  116.         :param subcast: Subtype or callable to cast return values as (used for
  117.                         nested structures).
  118.  
  119.         :returns: Value of type `cast`.
  120.         """
  121.         if cast is bool:
  122.             value = value.lower() in cls.BOOLEAN_TRUE_STRINGS
  123.         elif cast is float:
  124.             # Clean string
  125.             float_str = re.sub(r'[^\d,\.]', '', value)
  126.             # Split to handle thousand separator for different locales, i.e.
  127.             # comma or dot being the placeholder.
  128.             parts = re.split(r'[,\.]', float_str)
  129.             if len(parts) == 1:
  130.                 float_str = parts[0]
  131.             else:
  132.                 float_str = "{0}.{1}".format(''.join(parts[0:-1]), parts[-1])
  133.             value = float(float_str)
  134.         elif type(cast) is type and (issubclass(cast, list) or
  135.                                      issubclass(cast, tuple)):
  136.             value = (subcast(i.strip()) if subcast else i.strip() for i in
  137.                      value.split(',') if i)
  138.         elif cast is dict:
  139.             value = {k.strip(): subcast(v.strip()) if subcast else v.strip()
  140.                      for k, v in (i.split('=') for i in value.split(',') if
  141.                      value)}
  142.         try:
  143.             return cast(value)
  144.         except ValueError as error:
  145.             raise ConfigurationError(*error.args)
  146.  
  147.     # Shortcuts
  148.     bool = shortcut(bool)
  149.     dict = shortcut(dict)
  150.     float = shortcut(float)
  151.     int = shortcut(int)
  152.     list = shortcut(list)
  153.     set = shortcut(set)
  154.     str = shortcut(str)
  155.     tuple = shortcut(tuple)
  156.     json = shortcut(pyjson.loads)
  157.     url = shortcut(urlparse.urlparse)
  158.  
  159.     @staticmethod
  160.     def read_envfile(path=None, **overrides):
  161.         """
  162.         Read a .env file (line delimited KEY=VALUE) into os.environ.
  163.  
  164.         If not given a path to the file, recurses up the directory tree until
  165.         found.
  166.  
  167.         Uses code from Honcho (github.com/nickstenning/honcho) for parsing the
  168.         file.
  169.         """
  170.         if path is None:
  171.             frame = inspect.currentframe().f_back
  172.             caller_dir = os.path.dirname(frame.f_code.co_filename)
  173.             path = os.path.join(os.path.abspath(caller_dir), '.env')
  174.  
  175.         try:
  176.             with open(path, 'r') as f:
  177.                 content = f.read()
  178.         except getattr(__builtins__, 'FileNotFoundError', IOError):
  179.             logger.debug('envfile not found at %s, looking in parent dir.',
  180.                          path)
  181.             filedir, filename = os.path.split(path)
  182.             pardir = os.path.abspath(os.path.join(filedir, os.pardir))
  183.             path = os.path.join(pardir, filename)
  184.             if filedir != pardir:
  185.                 Env.read_envfile(path, **overrides)
  186.             else:
  187.                 # Reached top level directory.
  188.                 warnings.warn('Could not any envfile.')
  189.             return
  190.  
  191.         logger.debug('Reading environment variables from: %s', path)
  192.         for line in content.splitlines():
  193.             tokens = list(shlex.shlex(line, posix=True))
  194.             # parses the assignment statement
  195.             if len(tokens) < 3:
  196.                 continue
  197.             name, op = tokens[:2]
  198.             value = ''.join(tokens[2:])
  199.             if op != '=':
  200.                 continue
  201.             if not re.match(r'[A-Za-z_][A-Za-z_0-9]*', name):
  202.                 continue
  203.             value = value.replace(r'\n', '\n').replace(r'\t', '\t')
  204.             os.environ.setdefault(name, value)
  205.  
  206.         for name, value in overrides.items():
  207.             os.environ.setdefault(name, value)
  208.  
  209.  
  210. # Convenience object if no schema is required.
  211. env = Env()
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top