Skip to content

Strange errors #213

@JCHacking

Description

@JCHacking

I have the following code, which is used to read the configuration of both a file and environment variables.

"""Load Settings."""
import contextlib
import pathlib
from typing import cast

import dataconf


def load_settings[SETTINGS: object](
    cls: type[SETTINGS],
    *,
    file_path: str = "./settings.yaml",
    prefix_environment_variables: str = "",
) -> SETTINGS:
    """Load Settings.

    Args:
        cls: Settings Type
        file_path: File Path
        prefix_environment_variables: Prefix Environment Variables

    Returns:
        Settings
    """
    configuration: dataconf.main.Multi = dataconf.multi
    configuration = configuration.file(path=file_path, allow_missing=True)
    configuration = configuration.env(
        prefix=f"{prefix_environment_variables}__" if prefix_environment_variables else "", ignore_unexpected=True
    )

    return cast(
        "SETTINGS",
        configuration.on(cls),
    )

The problem is that when I run it, it fails with this error:

self = ConfigTree({'allusersprofile': 'C:\\ProgramData', 'appdata': 'C:\\Users\\jcmencia\\AppData\\Roaming', 'artifactory_pas...ogram Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\Tools\\', 'windir': 'C:\\WINDOWS', 'zes_enable_sysman': '1'})
key_path = [], value = ConfigTree({'C': '.venv/Scripts/poetry'}), append = False

    def _put(self, key_path, value, append=False):
        key_elt = key_path[0]
>       if len(key_path) == 1:
E       IndexError: list index out of range

This is by running it with poetry run in git bash on windows.

And if I run the same thing but in github actions with linux, I get this error:

self = '{', instring = '.venv/bin/poetry', loc = 16, do_actions = True
callPreParse = False

    def _parseNoCache(
        self, instring, loc, do_actions=True, callPreParse=True
    ) -> tuple[int, ParseResults]:
        debugging = self.debug  # and do_actions)
        len_instring = len(instring)
    
        if debugging or self.failAction:
            # print("Match {} at loc {}({}, {})".format(self, loc, lineno(loc, instring), col(loc, instring)))
            try:
                if callPreParse and self.callPreparse:
                    pre_loc = self.preParse(instring, loc)
                else:
                    pre_loc = loc
                tokens_start = pre_loc
                if self.debugActions.debug_try:
                    self.debugActions.debug_try(instring, tokens_start, self, False)
                if self.mayIndexError or pre_loc >= len_instring:
                    try:
                        loc, tokens = self.parseImpl(instring, pre_loc, do_actions)
                    except IndexError:
                        raise ParseException(instring, len_instring, self.errmsg, self)
                else:
                    loc, tokens = self.parseImpl(instring, pre_loc, do_actions)
            except Exception as err:
                # print("Exception raised:", err)
                if self.debugActions.debug_fail:
                    self.debugActions.debug_fail(
                        instring, tokens_start, self, err, False
                    )
                if self.failAction:
                    self.failAction(instring, tokens_start, self, err)
                raise
        else:
            if callPreParse and self.callPreparse:
                pre_loc = self.preParse(instring, loc)
            else:
                pre_loc = loc
            tokens_start = pre_loc
            if self.mayIndexError or pre_loc >= len_instring:
                try:
>                   loc, tokens = self.parseImpl(instring, pre_loc, do_actions)

.venv/lib/python3.13/site-packages/pyparsing/core.py:853: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = '{', instring = '.venv/bin/poetry', loc = 16, do_actions = True

    def parseImpl(self, instring, loc, do_actions=True) -> ParseImplReturnType:
>       if instring[loc] == self.firstMatchChar:
E       IndexError: string index out of range

.venv/lib/python3.13/site-packages/pyparsing/core.py:[250](...42946844622#step:7:251)2: IndexError

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "/home/runner/_work/automated-tests-app-sqa/automated-tests-app-sqa/.venv/lib/python3.13/site-packages/pyparsing/core.py", line 853, in _parseNoCache
    loc, tokens = self.parseImpl(instring, pre_loc, do_actions)
                  ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/_work/automated-tests-app-sqa/automated-tests-app-sqa/.venv/lib/python3.13/site-packages/pyparsing/core.py", line 2502, in parseImpl
    if instring[loc] == self.firstMatchChar:
       ~~~~~~~~^^^^^
IndexError: string index out of range

During handling of the above exception, another exception occurred:

pyparsing.exceptions.ParseException: Expected {: ... | {{'=' | ':' | '+='} - [Suppress:({{{'#' | '//'} - SkipTo:({Suppress:(W:(

)) | end of text})} | Suppress:(W:(

))})]... - ConcatenatedValueParser:([{{{{{Suppress:({[Suppress:(W:(

,))] {'#' | '//'} - SkipTo:({Suppress:(W:(

)) | end of text})}) | {Suppress:('include') {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')} | {'required' - Suppress:('(') - {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')}} - Suppress:(')')}}} | Re:('[ \t]*\$\{[^\}]+\}[ \t]*')} | : ...} | Forward: {{{{Suppress:('[') -} ListParser:({{ConcatenatedValueParser:([{{{{{Suppress:({[Suppress:(W:(

,))] {'#' | '//'} - SkipTo:({Suppress:(W:(

)) | end of text})}) | {Suppress:('include') {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')} | {'required' - Suppress:('(') - {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')}} - Suppress:(')')}}} | Re:('[ \t]*\$\{[^\}]+\}[ \t]*')} | : ...} | : ...} | {{{{{Combine:({{W:(0-9) Suppress:([' ']...)} {'ns' ^ 'nano' ^ 'nanos' ^ 'nanosecond' ^ 'nanoseconds' ^ 'us' ^ 'micro' ^ 'micros' ^ 'microsecond' ^ 'microseconds' ^ 'ms' ^ 'milli' ^ 'millis' ^ 'millisecond' ^ 'milliseconds' ^ 's' ^ 'second' ^ 'seconds' ^ 'm' ^ 'minute' ^ 'minutes' ^ 'h' ^ 'hour' ^ 'hours' ^ 'w' ^ 'week' ^ 'weeks' ^ 'd' ^ 'day' ^ 'days' ^ 'mo' ^ 'month' ^ 'months' ^ 'y' ^ 'year' ^ 'years'} Suppress:(end of a word)}) |} | {{{{{Combine:({{W:(0-9) Suppress:([' ']...)} {'ns' ^ 'nano' ^ 'nanos' ^ 'nanosecond' ^ 'nanoseconds' ^ 'us' ^ 'micro' ^ 'micros' ^ 'microsecond' ^ 'microseconds' ^ 'ms' ^ 'milli' ^ 'millis' ^ 'millisecond' ^ 'milliseconds' ^ 's' ^ 'second' ^ 'seconds' ^ 'm' ^ 'minute' ^ 'minutes' ^ 'h' ^ 'hour' ^ 'hours' ^ 'w' ^ 'week' ^ 'weeks' ^ 'd' ^ 'day' ^ 'days' ^ 'mo' ^ 'month' ^ 'months' ^ 'y' ^ 'year' ^ 'years'} Suppress:(end of a word)}) | Re:('[+-]?(\d*\.\d+|\d+(\.\d+)?)([eE][+\-]?\d+)?(?=$|[ \t]*([\$\}\],#\n\r]|//))')} | 'true'} | 'false'} | 'null'} | {{Re:('""".*?"*"""') | Re:('"(?:[^"\\\n]|\\.)*"[ \t]*')} | Re:('(?:[^^`+?!@*&"\[\{\s\]\}#,=\$\\]|\\.)+[ \t]*')}}} | Suppress:({{'\\' -} Suppress:(W:(

))})}]...)}}, found end of text  (at char 16), (line:1, col:17)

During handling of the above exception, another exception occurred:

prefix = ''
obj = environ({'ACTIONS_ID_TOKEN_REQUEST_TOKEN': '***', 'PYTEST_CURRENT_TEST': 'tests/rest/v1/test/test_create.py::test_definition_not_found (setup)'})

    def __env_vars_parse(prefix: str, obj: Dict[str, Any]):
        ret = {}
    
        def set_lens(p, focus, v):
            # value
            if len(p) == 1:
                # []x
                if isinstance(focus, list):
                    if not isinstance(p[0], int):
                        raise EnvListFormatException
                    if p[0] != len(focus):
                        raise EnvListOrderException
                    focus.append(v)
                # {}x
                else:
                    focus[p[0]] = v
                return
    
            # dict
            if p[1] == "":
                if p[0] not in focus:
                    # []{x}
                    if isinstance(focus, list):
                        if p[0] == len(focus):
                            focus.append({})
                    # {}{x}
                    else:
                        focus[p[0]] = {}
    
                return set_lens(p[2:], focus[p[0]], v)
    
            # list (only if the focus/value is already a list or if it starts with element 0)
            if isinstance(p[1], int) and (p[1] == 0 or isinstance(focus[p[0]], list)):
                if p[0] not in focus:
                    # [][x]
                    if isinstance(focus, list):
                        if p[1] != len(focus):
                            raise EnvListOrderException
                        focus.append([])
                    # {}[x]
                    else:
                        focus[p[0]] = []
    
                return set_lens(p[1:], focus[p[0]], v)
    
            # compose path
            return set_lens([f"{p[0]}_{p[1]}"] + p[2:], focus, v)
    
        def int_or_string(v):
            try:
                return int(v)
            except ValueError:
                return v
    
        if not prefix.endswith("_") and prefix != "":
            prefix = f"{prefix}_"
    
        for k, v in sorted(obj.items(), key=lambda x: x[0]):
            if k.startswith(prefix):
                if k.endswith("_"):
                    try:
>                       v = ConfigFactory.parse_string(v)

.venv/lib/python3.13/site-packages/dataconf/utils.py:389: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.venv/lib/python3.13/site-packages/pyhocon/config_parser.py:191: in parse_string
    return ConfigParser().parse(content, basedir, resolve, unresolved_value)
.venv/lib/python3.13/site-packages/pyhocon/config_parser.py:454: in parse
    config = config_expr.parseString(content, parseAll=True)[0]
.venv/lib/python3.13/site-packages/pyparsing/util.py:417: in _inner
    return fn(self, *args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = {[{Suppress:({[Suppress:(W:(

,))] {'#' | '//'} - SkipTo:({Suppress:(W:(

)) | end of text})}) | Suppress:(W:(

))}].....{Suppress:({[Suppress:(W:(

,))] {'#' | '//'} - SkipTo:({Suppress:(W:(

)) | end of text})}) | Suppress:(W:(

,))}]...}
instring = '.venv/bin/poetry', parse_all = False

    def parse_string(
        self, instring: str, parse_all: bool = False, *, parseAll: bool = False
    ) -> ParseResults:
        """
        Parse a string with respect to the parser definition. This function is intended as the primary interface to the
        client code.
    
        :param instring: The input string to be parsed.
        :param parse_all: If set, the entire input string must match the grammar.
        :param parseAll: retained for pre-PEP8 compatibility, will be removed in a future release.
        :raises ParseException: Raised if ``parse_all`` is set and the input string does not match the whole grammar.
        :returns: the parsed data as a :class:`ParseResults` object, which may be accessed as a `list`, a `dict`, or
          an object with attributes if the given parser includes results names.
    
        If the input string is required to match the entire grammar, ``parse_all`` flag must be set to ``True``. This
        is also equivalent to ending the grammar with :class:`StringEnd`\\ ().
    
        To report proper column numbers, ``parse_string`` operates on a copy of the input string where all tabs are
        converted to spaces (8 spaces per tab, as per the default in ``string.expandtabs``). If the input string
        contains tabs and the grammar uses parse actions that use the ``loc`` argument to index into the string
        being parsed, one can ensure a consistent view of the input string by doing one of the following:
    
        - calling ``parse_with_tabs`` on your grammar before calling ``parse_string`` (see :class:`parse_with_tabs`),
        - define your parse action using the full ``(s,loc,toks)`` signature, and reference the input string using the
          parse action's ``s`` argument, or
        - explicitly expand the tabs in your input string before calling ``parse_string``.
    
        Examples:
    
        By default, partial matches are OK.
    
        >>> res = Word('a').parse_string('aaaaabaaa')
        >>> print(res)
        ['aaaaa']
    
        The parsing behavior varies by the inheriting class of this abstract class. Please refer to the children
        directly to see more examples.
    
        It raises an exception if parse_all flag is set and instring does not match the whole grammar.
    
        >>> res = Word('a').parse_string('aaaaabaaa', parse_all=True)
        Traceback (most recent call last):
        ...
        pyparsing.ParseException: Expected end of text, found 'b'  (at char 5), (line:1, col:6)
        """
        parseAll = parse_all or parseAll
    
        ParserElement.reset_cache()
        if not self.streamlined:
            self.streamline()
        for e in self.ignoreExprs:
            e.streamline()
        if not self.keepTabs:
            instring = instring.expandtabs()
        try:
            loc, tokens = self._parse(instring, 0)
            if parseAll:
                loc = self.preParse(instring, loc)
                se = Empty() + StringEnd().set_debug(False)
                se._parse(instring, loc)
        except _ParseActionIndexError as pa_exc:
            raise pa_exc.exc
        except ParseBaseException as exc:
            if ParserElement.verbose_stacktrace:
                raise
    
            # catch and re-raise exception from here, clearing out pyparsing internal stack trace
>           raise exc.with_traceback(None)
E           pyparsing.exceptions.ParseSyntaxException: Expected {: ... | {{'=' | ':' | '+='} - [Suppress:({{{'#' | '//'} - SkipTo:({Suppress:(W:(
E           
)) | end of text})} | Suppress:(W:(
E           
))})]... - ConcatenatedValueParser:([{{{{{Suppress:({[Suppress:(W:(
E           
,))] {'#' | '//'} - SkipTo:({Suppress:(W:(
E           
)) | end of text})}) | {Suppress:('include') {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')} | {'required' - Suppress:('(') - {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')}} - Suppress:(')')}}} | Re:('[ \t]*\$\{[^\}]+\}[ \t]*')} | : ...} | Forward: {{{{Suppress:('[') -} ListParser:({{ConcatenatedValueParser:([{{{{{Suppress:({[Suppress:(W:(
E           
,))] {'#' | '//'} - SkipTo:({Suppress:(W:(
E           
)) | end of text})}) | {Suppress:('include') {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')} | {'required' - Suppress:('(') - {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')}} - Suppress:(')')}}} | Re:('[ \t]*\$\{[^\}]+\}[ \t]*')} | : ...} | : ...} | {{{{{Combine:({{W:(0-9) Suppress:([' ']...)} {'ns' ^ 'nano' ^ 'nanos' ^ 'nanosecond' ^ 'nanoseconds' ^ 'us' ^ 'micro' ^ 'micros' ^ 'microsecond' ^ 'microseconds' ^ 'ms' ^ 'milli' ^ 'millis' ^ 'millisecond' ^ 'milliseconds' ^ 's' ^ 'second' ^ 'seconds' ^ 'm' ^ 'minute' ^ 'minutes' ^ 'h' ^ 'hour' ^ 'hours' ^ 'w' ^ 'week' ^ 'weeks' ^ 'd' ^ 'day' ^ 'days' ^ 'mo' ^ 'month' ^ 'months' ^ 'y' ^ 'year' ^ 'years'} Suppress:(end of a word)}) |} | {{{{{Combine:({{W:(0-9) Suppress:([' ']...)} {'ns' ^ 'nano' ^ 'nanos' ^ 'nanosecond' ^ 'nanoseconds' ^ 'us' ^ 'micro' ^ 'micros' ^ 'microsecond' ^ 'microseconds' ^ 'ms' ^ 'milli' ^ 'millis' ^ 'millisecond' ^ 'milliseconds' ^ 's' ^ 'second' ^ 'seconds' ^ 'm' ^ 'minute' ^ 'minutes' ^ 'h' ^ 'hour' ^ 'hours' ^ 'w' ^ 'week' ^ 'weeks' ^ 'd' ^ 'day' ^ 'days' ^ 'mo' ^ 'month' ^ 'months' ^ 'y' ^ 'year' ^ 'years'} Suppress:(end of a word)}) | Re:('[+-]?(\d*\.\d+|\d+(\.\d+)?)([eE][+\-]?\d+)?(?=$|[ \t]*([\$\}\],#\n\r]|//))')} | 'true'} | 'false'} | 'null'} | {{Re:('""".*?"*"""') | Re:('"(?:[^"\\\n]|\\.)*"[ \t]*')} | Re:('(?:[^^`+?!@*&"\[\{\s\]\}#,=\$\\]|\\.)+[ \t]*')}}} | Suppress:({{'\\' -} Suppress:(W:(
E           
))})}]...)}}, found end of text  (at char 16), (line:1, col:17)

.venv/lib/python3.13/site-packages/pyparsing/core.py:1219: ParseSyntaxException

During handling of the above exception, another exception occurred:

request = <SubRequest 'run_app' for <Coroutine test_definition_not_found>>
kwargs = {'sql_database_migration': None}
func = <function run_app at 0x7f[334](...step:7:335)a238540>
event_loop_fixture_id = 'event_loop'
setup = <function _wrap_asyncgen_fixture.<locals>._asyncgen_fixture_wrapper.<locals>.setup at 0x7f3348f53ec0>
setup_task = <Task finished name='Task-3' coro=<_wrap_asyncgen_fixture.<locals>._asyncgen_fixture_wrapper.<locals>.setup() done, de...t]*\')}}} | Suppress:({{\'\\\\\' -} Suppress:(W:(\n\r))})}]...)}}, found end of text  (at char 16), (line:1, col:17)')>

    @functools.wraps(fixture)
    def _asyncgen_fixture_wrapper(request: FixtureRequest, **kwargs: Any):
        func = _perhaps_rebind_fixture_func(fixture, request.instance)
        event_loop_fixture_id = _get_event_loop_fixture_id_for_async_fixture(
            request, func
        )
        event_loop = request.getfixturevalue(event_loop_fixture_id)
        kwargs.pop(event_loop_fixture_id, None)
        gen_obj = func(**_add_kwargs(func, kwargs, event_loop, request))
    
        async def setup():
            res = await gen_obj.__anext__()  # type: ignore[union-attr]
            return res
    
        context = contextvars.copy_context()
        setup_task = _create_task_in_context(event_loop, setup(), context)
>       result = event_loop.run_until_complete(setup_task)

.venv/lib/python3.13/site-packages/pytest_asyncio/plugin.py:345: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../_tool/Python/3.13.3/x64/lib/python3.13/asyncio/base_events.py:719: in run_until_complete
    return future.result()
.venv/lib/python3.13/site-packages/pytest_asyncio/plugin.py:[340](step:7:341): in setup
    res = await gen_obj.__anext__()  # type: ignore[union-attr]
tests/conftest.py:111: in run_app
    await migrations.message_broker.run_async()
migrations/message_broker/main.py:10: in run_async
    await domain_events_configure()
migrations/message_broker/domain_events.py:12: in configure
    channel_pool=await SharedContainer.message_broker_channel_pool(),
.venv/lib/python3.13/site-packages/that_depends/providers/base.py:143: in __call__
    return await self.resolve()
.venv/lib/python3.13/site-packages/that_depends/providers/base.py:[356](#step:7:357): in resolve
    **{k: await v.resolve() if isinstance(v, AbstractProvider) else v for k, v in self._kwargs.items()},
.venv/lib/python3.13/site-packages/that_depends/providers/base.py:356: in resolve
    **{k: await v.resolve() if isinstance(v, AbstractProvider) else v for k, v in self._kwargs.items()},
.venv/lib/python3.13/site-packages/that_depends/providers/singleton.py:75: in resolve
    self._instance = self._factory(
.venv/lib/python3.13/site-packages/py_core_lib_sqa/setting/load_settings.py:30: in load_settings
    configuration = configuration.env(
.venv/lib/python3.13/site-packages/dataconf/main.py:49: in env
    data = env_vars_parse(prefix, os.environ)
.venv/lib/python3.13/site-packages/dataconf/main.py:34: in env_vars_parse
    return utils.__env_vars_parse(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

prefix = ''
obj = environ({'ACTIONS_ID_TOKEN_REQUEST_TOKEN': '***', 'PYTEST_CURRENT_TEST': 'tests/rest/v1/test/test_create.py::test_definition_not_found (setup)'})

    def __env_vars_parse(prefix: str, obj: Dict[str, Any]):
        ret = {}
    
        def set_lens(p, focus, v):
            # value
            if len(p) == 1:
                # []x
                if isinstance(focus, list):
                    if not isinstance(p[0], int):
                        raise EnvListFormatException
                    if p[0] != len(focus):
                        raise EnvListOrderException
                    focus.append(v)
                # {}x
                else:
                    focus[p[0]] = v
                return
    
            # dict
            if p[1] == "":
                if p[0] not in focus:
                    # []{x}
                    if isinstance(focus, list):
                        if p[0] == len(focus):
                            focus.append({})
                    # {}{x}
                    else:
                        focus[p[0]] = {}
    
                return set_lens(p[2:], focus[p[0]], v)
    
            # list (only if the focus/value is already a list or if it starts with element 0)
            if isinstance(p[1], int) and (p[1] == 0 or isinstance(focus[p[0]], list)):
                if p[0] not in focus:
                    # [][x]
                    if isinstance(focus, list):
                        if p[1] != len(focus):
                            raise EnvListOrderException
                        focus.append([])
                    # {}[x]
                    else:
                        focus[p[0]] = []
    
                return set_lens(p[1:], focus[p[0]], v)
    
            # compose path
            return set_lens([f"{p[0]}_{p[1]}"] + p[2:], focus, v)
    
        def int_or_string(v):
            try:
                return int(v)
            except ValueError:
                return v
    
        if not prefix.endswith("_") and prefix != "":
            prefix = f"{prefix}_"
    
        for k, v in sorted(obj.items(), key=lambda x: x[0]):
            if k.startswith(prefix):
                if k.endswith("_"):
                    try:
                        v = ConfigFactory.parse_string(v)
                    except pyparsing.ParseBaseException as e:
>                       raise ParseException(
                            f"env var {k} ends with `_` and expects a nested config, got: {e}"
                        )
E                       dataconf.exceptions.ParseException: env var _ ends with `_` and expects a nested config, got: Expected {: ... | {{'=' | ':' | '+='} - [Suppress:({{{'#' | '//'} - SkipTo:({Suppress:(W:(
E                       
)) | end of text})} | Suppress:(W:(
E                       
))})]... - ConcatenatedValueParser:([{{{{{Suppress:({[Suppress:(W:(
E                       
,))] {'#' | '//'} - SkipTo:({Suppress:(W:(
E                       
)) | end of text})}) | {Suppress:('include') {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')} | {'required' - Suppress:('(') - {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')}} - Suppress:(')')}}} | Re:('[ \t]*\$\{[^\}]+\}[ \t]*')} | : ...} | Forward: {{{{Suppress:('[') -} ListParser:({{ConcatenatedValueParser:([{{{{{Suppress:({[Suppress:(W:(
E                       
,))] {'#' | '//'} - SkipTo:({Suppress:(W:(
E                       
)) | end of text})}) | {Suppress:('include') {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')} | {'required' - Suppress:('(') - {Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') | {{'url' | 'file' | 'package'} - Suppress:('(') - Re:('"(?:[^"\\\n]|\\.)*"[ \t]*') - Suppress:(')')}} - Suppress:(')')}}} | Re:('[ \t]*\$\{[^\}]+\}[ \t]*')} | : ...} | : ...} | {{{{{Combine:({{W:(0-9) Suppress:([' ']...)} {'ns' ^ 'nano' ^ 'nanos' ^ 'nanosecond' ^ 'nanoseconds' ^ 'us' ^ 'micro' ^ 'micros' ^ 'microsecond' ^ 'microseconds' ^ 'ms' ^ 'milli' ^ 'millis' ^ 'millisecond' ^ 'milliseconds' ^ 's' ^ 'second' ^ 'seconds' ^ 'm' ^ 'minute' ^ 'minutes' ^ 'h' ^ 'hour' ^ 'hours' ^ 'w' ^ 'week' ^ 'weeks' ^ 'd' ^ 'day' ^ 'days' ^ 'mo' ^ 'month' ^ 'months' ^ 'y' ^ 'year' ^ 'years'} Suppress:(end of a word)}) |} | {{{{{Combine:({{W:(0-9) Suppress:([' ']...)} {'ns' ^ 'nano' ^ 'nanos' ^ 'nanosecond' ^ 'nanoseconds' ^ 'us' ^ 'micro' ^ 'micros' ^ 'microsecond' ^ 'microseconds' ^ 'ms' ^ 'milli' ^ 'millis' ^ 'millisecond' ^ 'milliseconds' ^ 's' ^ 'second' ^ 'seconds' ^ 'm' ^ 'minute' ^ 'minutes' ^ 'h' ^ 'hour' ^ 'hours' ^ 'w' ^ 'week' ^ 'weeks' ^ 'd' ^ 'day' ^ 'days' ^ 'mo' ^ 'month' ^ 'months' ^ 'y' ^ 'year' ^ 'years'} Suppress:(end of a word)}) | Re:('[+-]?(\d*\.\d+|\d+(\.\d+)?)([eE][+\-]?\d+)?(?=$|[ \t]*([\$\}\],#\n\r]|//))')} | 'true'} | 'false'} | 'null'} | {{Re:('""".*?"*"""') | Re:('"(?:[^"\\\n]|\\.)*"[ \t]*')} | Re:('(?:[^^`+?!@*&"\[\{\s\]\}#,=\$\\]|\\.)+[ \t]*')}}} | Suppress:({{'\\' -} Suppress:(W:(
E                       
))})}]...)}}, found end of text  (at char 16), (line:1, col:17)

.venv/lib/python3.13/site-packages/dataconf/utils.py:[391](:392): ParseException
---------------------------- Captured stderr setup -----------------------------
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 71f092c99a7d, Create Tests table.

As far as I have been able to see in both cases it is for having the prefix empty. Could an exception be added to indicate that it is not allowed to be empty? Or make it permissible to have it empty?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions