Beispiel #1
0
def parse_env_file(envfile):
    """Parse the content of an iterable of lines as .env

    Return a dict of config variables.

    >>> parse_env_file(['DUDE=Abides'])
    {'DUDE': 'Abides'}

    """
    data = {}
    for line_no, line in enumerate(envfile):
        line = line.strip()
        if not line or line.startswith('#'):
            continue
        if '=' not in line:
            raise ConfigurationError(
                'Env file line missing = operator (line %s)' % (line_no + 1))
        k, v = line.split('=', 1)
        k = k.strip()
        if not ENV_KEY_RE.match(k):
            raise ConfigurationError(
                'Invalid variable name "%s" in env file (line %s)' %
                (k, (line_no + 1)))
        v = v.strip().strip('\'"')
        data[k] = v

    return data
Beispiel #2
0
def parse_env_file(envfile):
    """Parse the content of an iterable of lines as ``.env``.

    Return a dict of config variables.

    >>> parse_env_file(['DUDE=Abides'])
    {'DUDE': 'Abides'}

    """
    data = {}
    for line_no, line in enumerate(envfile):
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            raise ConfigurationError(
                "Env file line missing = operator (line %s)" % (line_no + 1)
            )
        k, v = line.split("=", 1)
        k = k.strip()
        if not ENV_KEY_RE.match(k):
            raise ConfigurationError(
                'Invalid variable name "{}" in env file (line {})'.format(
                    k, (line_no + 1)
                )
            )
        v = v.strip().strip("'\"")
        data[k] = v

    return data
Beispiel #3
0
        def traverse(namespace, d):
            cfg = {}
            for key, val in d.items():
                if isinstance(d[key], dict):
                    cfg.update(traverse(namespace + [key], d[key]))
                else:
                    if not isinstance(val, str):
                        # All values should be double-quoted strings so they
                        # parse as strings; anything else is a configuration
                        # error at parse-time
                        raise ConfigurationError(
                            'Invalid value %r in file %s: values must be double-quoted strings'
                            % (val, path))

                    cfg['_'.join(namespace + [key]).upper()] = val

            return cfg
Beispiel #4
0
    def __call__(self,
                 key,
                 namespace=None,
                 default=NO_VALUE,
                 alternate_keys=NO_VALUE,
                 doc='',
                 parser=str,
                 raise_error=True,
                 raw_value=False):
        """Returns a parsed value from the environment

        :arg key: the key to look up

        :arg namespace: the namespace for the key--different environments
            use this differently

        :arg default: the default value (if any); must be a string that is
            parseable by the specified parser; if no default is provided, this
            will raise an error or return ``everett.NO_VALUE`` depending on
            the value of ``raise_error``

        :arg alternate_keys: the list of alternate keys to look up;
            supports a ``root:`` key prefix which will cause this to look at
            the configuration root rather than the current namespace

            .. versionadded:: 0.3

        :arg doc: documentation for this config option

            .. versionadded:: 0.6

        :arg parser: the parser for converting this value to a Python object

        :arg raise_error: True if you want a lack of value to raise a
            ``everett.ConfigurationError``

        :arg raw_value: True if you want the raw unparsed value, False otherwise

        :raises everett.ConfigurationMissingError: if the required bit of configuration
            is missing from all the environments

        :raises everett.InvalidKeyError: if the configuration key doesn't exist for
            that component

        :raises everet.InvalidValueError: (Python 3-only) if the configuration value
            is invalid in some way (not an integer, not a bool, etc)

        :raises Exception subclass: (Python 2-only) parser code can raise
            anything and since this is Python 2, we can't do much about it
            without stomping on the traceback so we change the message and
            raise the same exception

        Examples::

            config = ConfigManager([])

            # Use the special bool parser
            DEBUG = config('DEBUG', default='True', parser=bool)

            # Use the list of parser
            from everett.manager import ListOf
            ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost',
                                   parser=ListOf(str))

            # Use alternate_keys for backwards compatibility with an
            # older version of this software
            PASSWORD = config('PASSWORD', alternate_keys=['SECRET'])


        The default value should always be a string that is parseable by the
        parser. This simplifies some things because then default values are
        **always** strings and parseable by parsers. It's not the case that in
        some places they're strings and some places they're parsed values.

        The parser can be any callable that takes a string value and returns a
        parsed value.

        """
        if not (default is NO_VALUE or isinstance(default, six.string_types)):
            raise ConfigurationError('default value %r is not a string' %
                                     (default, ))

        if raw_value:
            # If we're returning raw values, then we can just use str which is
            # a no-op.
            parser = str
        else:
            parser = get_parser(parser)

        def build_msg(*pargs):
            return '\n'.join([item for item in pargs if item])

        # Go through all possible keys
        all_keys = [key]
        if alternate_keys is not NO_VALUE:
            all_keys = all_keys + alternate_keys

        for possible_key in all_keys:
            if possible_key.startswith('root:'):
                # If this is a root-anchored key, we drop the namespace.
                possible_key = possible_key[5:]
                use_namespace = None
            else:
                use_namespace = namespace

            # Go through environments in reverse order
            for env in self.envs:
                val = env.get(possible_key, use_namespace)

                if val is not NO_VALUE:
                    try:
                        return parser(val)
                    except ConfigurationError:
                        # Re-raise ConfigurationError and friends since that's
                        # what we want to be raising.
                        raise
                    except Exception as orig_exc:
                        exc_info = sys.exc_info()
                        msg = build_msg(
                            '%(class)s: %(msg)s' % {
                                'class': exc_info[0].__name__,
                                'msg': str(exc_info[1])
                            },
                            'namespace=%(namespace)s key=%(key)s requires a value parseable by %(parser)s'
                            % {  # noqa
                                'namespace': use_namespace,
                                'key': key,
                                'parser': qualname(parser)
                            },
                            doc,
                            self.doc)

                        if six.PY3:
                            # Python 3 has exception chaining, so this is easy peasy
                            raise InvalidValueError(msg, namespace, key,
                                                    parser)
                        else:
                            # Python 2 does not have exception chaining, so we're going
                            # to break our promise that this always returns a
                            # ConfigurationError, modify the message and then raise it
                            # again.
                            if orig_exc.args:
                                orig_exc.args = tuple([msg] +
                                                      list(orig_exc.args[1:]))
                            else:
                                orig_exc.args = tuple(msg)
                            raise

        # Return the default if there is one
        if default is not NO_VALUE:
            try:
                return parser(default)
            except ConfigurationError:
                # Re-raise ConfigurationError and friends since that's
                # what we want to be raising.
                raise
            except Exception as orig_exc:
                # FIXME(willkg): This is a programmer error--not a user
                # configuration error. We might want to denote that better.
                exc_info = sys.exc_info()
                msg = build_msg(
                    '%(class)s: %(msg)s' % {
                        'class': exc_info[0].__name__,
                        'msg': str(exc_info[1])
                    },
                    'namespace=%(namespace)s key=%(key)s requires a default value parseable by %(parser)s'
                    % {  # noqa
                        'namespace': namespace,
                        'key': key,
                        'parser': qualname(parser)
                    },
                    doc,
                    self.doc)

                if six.PY3:
                    # Python 3 has exception chaining, so this is easy peasy
                    raise InvalidValueError(msg, namespace, key, parser)
                else:
                    # Python 2 does not have exception chaining, so we're going
                    # to break our promise that this always returns a
                    # ConfigurationError, modify the message and then raise it
                    # again.
                    if orig_exc.args:
                        orig_exc.args = tuple([msg] + list(orig_exc.args[1:]))
                    else:
                        orig_exc.args = tuple(msg)
                    raise

        # No value specified and no default, so raise an error to the user
        if raise_error:
            msg = build_msg(
                'namespace=%(namespace)s key=%(key)s requires a value parseable by %(parser)s'
                % {
                    'namespace': namespace,
                    'key': key,
                    'parser': qualname(parser)
                }, doc, self.doc)

            raise ConfigurationMissingError(msg, namespace, key, parser)

        # Otherwise return NO_VALUE
        return NO_VALUE
Beispiel #5
0
    def __call__(
        self,
        key,
        namespace=None,
        default=NO_VALUE,
        alternate_keys=NO_VALUE,
        doc="",
        parser=str,
        raise_error=True,
        raw_value=False,
    ):
        """Return a parsed value from the environment.

        :arg key: the key to look up

        :arg namespace: the namespace for the key--different environments
            use this differently

        :arg default: the default value (if any); this must be a string that is
            parseable by the specified parser; if no default is provided, this
            will raise an error or return ``everett.NO_VALUE`` depending on
            the value of ``raise_error``

        :arg alternate_keys: the list of alternate keys to look up;
            supports a ``root:`` key prefix which will cause this to look at
            the configuration root rather than the current namespace

            .. versionadded:: 0.3

        :arg doc: documentation for this config option

            .. versionadded:: 0.6

        :arg parser: the parser for converting this value to a Python object

        :arg raise_error: True if you want a lack of value to raise a
            ``everett.ConfigurationError``

        :arg raw_value: True if you want the raw unparsed value, False otherwise

        :raises everett.ConfigurationMissingError: if the required bit of configuration
            is missing from all the environments

        :raises everett.InvalidKeyError: if the configuration key doesn't exist for
            that component

        :raises everet.InvalidValueError: (Python 3-only) if the configuration value
            is invalid in some way (not an integer, not a bool, etc)

        :raises Exception subclass: (Python 2-only) parser code can raise
            anything and since this is Python 2, we can't do much about it
            without stomping on the traceback so we change the message and
            raise the same exception

        Examples::

            config = ConfigManager([])

            # Use the special bool parser
            DEBUG = config('DEBUG', default='false', parser=bool)
            DEBUG = config('DEBUG', default='True', parser=bool)
            DEBUG = config('DEBUG', default='true', parser=bool)
            DEBUG = config('DEBUG', default='yes', parser=bool)
            DEBUG = config('DEBUG', default='y', parser=bool)

            # Use the list of parser
            from everett.manager import ListOf
            ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost',
                                   parser=ListOf(str))

            # Use alternate_keys for backwards compatibility with an
            # older version of this software
            PASSWORD = config('PASSWORD', alternate_keys=['SECRET'])


        The default value should **always** be a string that is parseable by the
        parser. This simplifies thinking about values since **all** values
        are strings that are parsed by the parser rather than default values
        do one thing and non-default values doa nother. Further, it simplifies
        documentation for the user since the default value is an example value.

        The parser can be any callable that takes a string value and returns a
        parsed value.

        """
        if not (default is NO_VALUE or isinstance(default, str)):
            raise ConfigurationError(f"default value {default!r} is not a string")

        if raw_value:
            # If we're returning raw values, then we can just use str which is
            # a no-op.
            parser = str
        else:
            parser = get_parser(parser)

        def build_msg(*pargs):
            return "\n".join([item for item in pargs if item])

        # Go through all possible keys
        all_keys = [key]
        if alternate_keys is not NO_VALUE:
            all_keys = all_keys + alternate_keys

        for possible_key in all_keys:
            if possible_key.startswith("root:"):
                # If this is a root-anchored key, we drop the namespace.
                possible_key = possible_key[5:]
                use_namespace = None
            else:
                use_namespace = namespace

            logger.debug(
                "Looking up key: %s, namespace: %s", possible_key, use_namespace
            )

            # Go through environments in reverse order
            for env in self.envs:
                val = env.get(possible_key, use_namespace)

                if val is not NO_VALUE:
                    try:
                        parsed_val = parser(val)
                        logger.debug("Returning raw: %r, parsed: %r", val, parsed_val)
                        return parsed_val
                    except ConfigurationError:
                        # Re-raise ConfigurationError and friends since that's
                        # what we want to be raising.
                        raise
                    except Exception:
                        exc_info = sys.exc_info()
                        msg = build_msg(
                            "%(class)s: %(msg)s"
                            % {"class": exc_info[0].__name__, "msg": str(exc_info[1])},
                            "namespace=%(namespace)s key=%(key)s requires a value parseable by %(parser)s"
                            % {  # noqa
                                "namespace": use_namespace,
                                "key": key,
                                "parser": qualname(parser),
                            },
                            doc,
                            self.doc,
                        )

                        raise InvalidValueError(msg, namespace, key, parser)

        # Return the default if there is one
        if default is not NO_VALUE:
            try:
                parsed_val = parser(default)
                logger.debug(
                    "Returning default raw: %r, parsed: %r", default, parsed_val
                )
                return parsed_val
            except ConfigurationError:
                # Re-raise ConfigurationError and friends since that's
                # what we want to be raising.
                raise
            except Exception:
                # FIXME(willkg): This is a programmer error--not a user
                # configuration error. We might want to denote that better.
                exc_info = sys.exc_info()
                msg = build_msg(
                    "%(class)s: %(msg)s"
                    % {"class": exc_info[0].__name__, "msg": str(exc_info[1])},
                    "namespace=%(namespace)s key=%(key)s requires a default value parseable by %(parser)s"
                    % {  # noqa
                        "namespace": namespace,
                        "key": key,
                        "parser": qualname(parser),
                    },
                    doc,
                    self.doc,
                )

                raise InvalidValueError(msg, namespace, key, parser)

        # No value specified and no default, so raise an error to the user
        if raise_error:
            msg = build_msg(
                "namespace=%(namespace)s key=%(key)s requires a value parseable by %(parser)s"
                % {"namespace": namespace, "key": key, "parser": qualname(parser)},
                doc,
                self.doc,
            )

            raise ConfigurationMissingError(msg, namespace, key, parser)

        logger.debug("Found nothing--returning NO_VALUE")
        # Otherwise return NO_VALUE
        return NO_VALUE