Found while reviewing #102. All of the following reproduce on main (e797060, 5.4.1) in src/mxdev/including.py (resolve_dependencies).
1. Include paths are parsed with a different interpolation than the final config
resolve_dependencies reads each file with a plain ConfigParser() (BasicInterpolation), while read_with_included parses the final config with ExtendedInterpolation.
${...} is not resolved in include paths. With d = sub and include = ${settings:d}/c.ini, the resolver opens the literal path ${settings:d}/c.ini and raises FileNotFoundError, although the final config would show include = sub/c.ini.
- A
% in an include path or URL (50%.ini, URL-encoded %20) raises configparser.InterpolationSyntaxError: '%' must be followed by '%' or '('. An escaped %% is read as % by the resolver but stays %% in the final config.
Possible fix: use ExtendedInterpolation in the resolver too (include values are resolved the same way as in the final config), or at least interpolation=None.
2. No cycle detection
mx.ini with include = b.ini, and b.ini with include = mx.ini, gives RecursionError: maximum recursion depth exceeded. A set of already visited (resolved) paths/URLs with a clear error message would help. This becomes more likely with include-optional (#102), where a custom file naturally refers back to the main file.
3. Relative includes ignore redirects
http_parent is built from the requested URL, not from the final URL after redirects (fio.geturl()). If https://example.com/latest/mx.ini redirects to https://cdn.example.com/v5/config/mx.ini, a relative include = other.ini in it is fetched from https://example.com/latest/other.ini instead of the sibling of the redirect target.
Assisted-by: Claude Opus 5.5
Found while reviewing #102. All of the following reproduce on main (e797060, 5.4.1) in
src/mxdev/including.py(resolve_dependencies).1. Include paths are parsed with a different interpolation than the final config
resolve_dependenciesreads each file with a plainConfigParser()(BasicInterpolation), whileread_with_includedparses the final config withExtendedInterpolation.${...}is not resolved in include paths. Withd = subandinclude = ${settings:d}/c.ini, the resolver opens the literal path${settings:d}/c.iniand raisesFileNotFoundError, although the final config would showinclude = sub/c.ini.%in an include path or URL (50%.ini, URL-encoded%20) raisesconfigparser.InterpolationSyntaxError: '%' must be followed by '%' or '('. An escaped%%is read as%by the resolver but stays%%in the final config.Possible fix: use
ExtendedInterpolationin the resolver too (include values are resolved the same way as in the final config), or at leastinterpolation=None.2. No cycle detection
mx.iniwithinclude = b.ini, andb.iniwithinclude = mx.ini, givesRecursionError: maximum recursion depth exceeded. A set of already visited (resolved) paths/URLs with a clear error message would help. This becomes more likely withinclude-optional(#102), where a custom file naturally refers back to the main file.3. Relative includes ignore redirects
http_parentis built from the requested URL, not from the final URL after redirects (fio.geturl()). Ifhttps://example.com/latest/mx.iniredirects tohttps://cdn.example.com/v5/config/mx.ini, a relativeinclude = other.iniin it is fetched fromhttps://example.com/latest/other.iniinstead of the sibling of the redirect target.Assisted-by: Claude Opus 5.5