Conversation
There was a problem hiding this comment.
Pull request overview
This draft replaces Robot Framework’s internal getopt-based CLI parsing with the confargs resolution engine, enabling TOML-based configuration file support (e.g., robot.toml / pyproject.toml) while aiming to preserve existing CLI behavior and output.
Changes:
- Swapped the core argument parsing/resolution to
confargs.ConfigurationProcessor, keeping Robot’s usage-string-driven option introspection intact. - Added TOML configuration file discovery/processing and precedence (CLI > env > config > defaults).
- Added unit tests covering basic config-file behavior and added a runtime dependency on
confargs.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/robot/utils/argumentparser.py |
Replaces getopt parsing with confargs + canonicalization layer and config-file support. |
utest/utils/test_argumentparser.py |
Adds unit tests for configuration file reading and precedence. |
setup.py |
Adds confargs>=0.5 as a runtime dependency. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
e257632 to
3fbf694
Compare
| def __init__( | ||
| self, | ||
| config, | ||
| usage, | ||
| name=None, | ||
| version=None, | ||
| arg_limits=None, | ||
| validator=None, | ||
| env_options=None, | ||
| ): | ||
| if not usage: | ||
| raise FrameworkError("Usage cannot be empty") | ||
| self._config = config | ||
| self._usage = usage | ||
| self.name = name or usage.splitlines()[0].split(" -- ")[0].strip() | ||
| self.version = version or get_full_version() | ||
| self._arg_limit_validator = ArgLimitValidator(arg_limits) | ||
| self._validator = validator | ||
| # ``env_options`` (ROBOT_OPTIONS / REBOT_OPTIONS) is read by confargs | ||
| # itself via the ``options_env_var`` class attribute on the config. | ||
|
|
3fbf694 to
d72036f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Suppressed comments (1)
src/robot/utils/confargsparser.py:76
env_optionsis accepted (and always passed fromApplication) but currently ignored, which makesApplication(env_options=...)a no-op when usingConfargsParser. To preserve the legacy contract and avoid silent mismatches, wireenv_optionsinto the confargs config (or validate it).
self._config = config
self._usage = usage
self.name = name or usage.splitlines()[0].split(" -- ")[0].strip()
self.version = version or get_full_version()
self._arg_limit_validator = ArgLimitValidator(arg_limits)
| Every value option defaults to ``None`` and every repeatable option to an empty | ||
| list so that options the user did not give are filtered out before reaching the | ||
| settings objects, which then apply their own defaults -- keeping behaviour | ||
| identical to the legacy parser. |
| def parser(config=RobotArgs, arg_limits=(1,), validator=None): | ||
| return ConfargsParser( | ||
| config, | ||
| USAGE, | ||
| arg_limits=arg_limits, | ||
| validator=validator, | ||
| env_options=None, | ||
| ) |
d72036f to
97c392a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/robot/utils/confargsparser.py:69
env_optionsis accepted (and passed fromApplication) but not actually used byConfargsParser(it’s only mentioned in a comment). This is easy to misconfigure for any futureApplication(..., config=...)consumers who expectenv_options=to have an effect. Consider either (1) removingenv_optionsfromConfargsParserand not passing it fromApplication, or (2) validating thatenv_optionsmatches the config’soptions_env_varand raising a clear error if not.
def __init__(
self,
config,
usage,
name=None,
version=None,
arg_limits=None,
validator=None,
env_options=None,
):
| self._ap = ConfargsParser( | ||
| config, | ||
| usage, | ||
| name, | ||
| version, | ||
| arg_limits, | ||
| self.validate, | ||
| env_options, | ||
| ) |
| namespace = ConfigurationProcessor( | ||
| self._config, | ||
| argv=args, | ||
| environ=None, | ||
| cwd=Path.cwd(), | ||
| ).process() |
97c392a to
4bcfd3b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/robot/utils/confargsparser.py:96
- New functionality here relies on confargs merging sources (CLI > env var > config file). There are currently no unit tests exercising (1)
ROBOT_OPTIONS/REBOT_OPTIONSenv var ingestion/precedence and (2) config-file discovery/precedence (robot.toml/pyproject.toml,--no-config,--config, profiles). Adding focused tests would help ensure the behavior stays compatible with the legacy parser and matches the PR’s precedence guarantees.
args = [system_decode(a) for a in args]
try:
namespace = ConfigurationProcessor(
self._config,
argv=args,
environ=None,
cwd=Path.cwd(),
).process()
4bcfd3b to
6a4175f
Compare
6a4175f to
035c99e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
src/robot/utils/confargsparser.py:95
ConfigurationProcessoris currently called withenviron=None, whileApplicationstill passesenv_optionsand the PR description saysROBOT_OPTIONS/REBOT_OPTIONSremain supported. To make env-var handling unambiguous (and avoid accidentally disabling it ifNonemeans "no environment" in confargs), passos.environexplicitly (or drop the override) and consider either usingenv_optionsor removing it from the constructor to avoid a misleading API.
namespace = ConfigurationProcessor(
self._config,
argv=args,
environ=None,
cwd=Path.cwd(),
src/robot/conf/arguments.py:183
cli_allow_abbrevis enabled forrebotas well. This conflicts with the PR description's statement that unique-prefix long-option abbreviations are intentionally dropped; please align the implementation and the documented compatibility expectations.
cli_case_insensitive = True
cli_ignore_hyphens = True
cli_allow_abbrev = True
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/robot/utils/confargsparser.py:69
ConfargsParser.__init__()acceptsenv_options(andApplicationpasses it), but the value is currently ignored. This creates a silent mismatch risk if the config’soptions_env_vardiffers (or isn’t set), and it diverges fromArgumentParserwhereenv_optionsdirectly controls env var parsing.
arg_limits=None,
validator=None,
env_options=None,
):
035c99e to
c86c27f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
src/robot/utils/confargsparser.py:80
env_optionsis accepted byConfargsParser.__init__(and is passed in fromApplication), but it is currently ignored. This creates a silent mismatch withArgumentParser, whereenv_optionscontrols which env var is consulted, and it can also hide configuration mistakes ifenv_optionsdiffers fromconfig.options_env_var.
Either remove this parameter entirely for the confargs-backed path, or validate that it matches the ArgConfig’s options_env_var to avoid silently ignoring the caller’s intent.
def __init__(
self,
config,
usage,
name=None,
version=None,
arg_limits=None,
validator=None,
env_options=None,
):
if not usage:
raise FrameworkError("Usage cannot be empty")
self._config = config
self._usage = usage
self.name = name or usage.splitlines()[0].split(" -- ")[0].strip()
self.version = version or get_full_version()
self._arg_limit_validator = ArgLimitValidator(arg_limits)
self._validator = validator
# ``env_options`` (ROBOT_OPTIONS / REBOT_OPTIONS) is read by confargs
# itself via the ``options_env_var`` class attribute on the config.
| # --- Positional arguments ---------------------------------------------- | ||
| data_sources: list[str] = argument(name="data-sources", nargs="*") | ||
|
|
c86c27f to
82cfd80
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/robot/utils/confargsparser.py:76
env_optionsis accepted (andApplicationpasses it) but it currently has no effect:config.options_env_varis never validated/overridden, so a mismatch would be silently ignored. Either validate thatenv_optionsmatchesconfig.options_env_varor derive a wrapper config that setsoptions_env_varfromenv_optionsto keep the parser adapter contract accurate.
self._config = config
self._usage = usage
self.name = name or usage.splitlines()[0].split(" -- ")[0].strip()
self.version = version or get_full_version()
self._arg_limit_validator = ArgLimitValidator(arg_limits)
| if config is not None: | ||
| self._ap = ConfargsParser( | ||
| config, | ||
| usage, | ||
| name, | ||
| version, | ||
| arg_limits, | ||
| self.validate, | ||
| env_options, | ||
| ) | ||
| else: |
| if value.upper() == "STDIN": | ||
| return split_argument_file(sys.stdin.read()) | ||
| return read_argument_file(value) |
82cfd80 to
61c4067
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/robot/utils/application.py:46
- When
configis provided,Application.__init__still accepts**auto_optionsbut silently ignores them (they’re only forwarded toArgumentParser). This can hide mistakes likeauto_help=Falsehaving no effect with the confargs-based parser. Consider failing fast whenauto_optionsare passed together withconfig(or add equivalent support inConfargsParser).
if config is not None:
self._ap = ConfargsParser(
setup.py:78
- PR description states
setup.pyadds aconfargs>=0.7dependency, but the code pinsconfargs>=0.8(and test requirements also use>=0.8). Please align the PR description with the actual minimum version (or adjust the minimum if 0.8 features aren’t required).
install_requires=[
# Declarative CLI parsing and configuration-file support. confargs
# requires Python 3.10+, which is why Robot Framework's minimum
# supported Python version is raised to 3.10 in this change.
"confargs>=0.8",
],
…port Replace the getopt-based command-line parsing of the `robot` and `rebot` tools with the declarative confargs library, and add configuration-file support (addresses robotframework#5337). The option set of each tool is now declared explicitly as a confargs `ArgConfig` subclass (`RobotArgs` / `RebotArgs` in `robot.conf.arguments`), and a thin `ConfargsParser` (`robot.utils.confargsparser`) adapts confargs to the existing `Application` parsing contract (`parse_args` -> options dict + globbed data sources, `Information` for `--help`/`--version`, `DataError` on failure). `Application` gained an optional `config=` parameter selecting the new parser; `libdoc`/`testdoc` keep using the legacy `ArgumentParser`. Configuration is merged with precedence: command line > environment variables > config file > option default. Config files are `[tool.robot]` / `[tool.rebot]` tables discovered upward from the working directory (and the user config dir), with `--config`, `--no-config`, `--profile` and `--ignore-git` controls provided by confargs. Behaviour of value/multi/flag options and their defaults is preserved so the downstream `RobotSettings`/`RebotSettings` defaulting is unchanged. Options now use their canonical spelling only: names are no longer case- or hyphen-insensitive and cannot be abbreviated, and boolean flags negate with `--no-<name>` (e.g. `--no-statusrc` instead of `--nostatusrc`). See the pull request description for the full migration notes and remaining TODOs. Co-authored-by: Copilot <[email protected]>
61c4067 to
d379d4f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Suppressed comments (2)
src/robot/utils/confargsparser.py:80
env_optionsis accepted byConfargsParser.__init__(and is always passed byApplication), but it currently has no effect: confargs reads the env var name fromconfig.options_env_varinstead. This makesApplication(..., config=..., env_options=...)behave unexpectedly if the caller passes a different env var name. Either validate/raise on mismatch or adapt the config used by confargs soenv_optionsis honored.
def __init__(
self,
config,
usage,
name=None,
version=None,
arg_limits=None,
validator=None,
env_options=None,
):
if not usage:
raise FrameworkError("Usage cannot be empty")
self._config = config
self._usage = usage
self.name = name or usage.splitlines()[0].split(" -- ")[0].strip()
self.version = version or get_full_version()
self._arg_limit_validator = ArgLimitValidator(arg_limits)
self._validator = validator
# ``env_options`` (ROBOT_OPTIONS / REBOT_OPTIONS) is read by confargs
# itself via the ``options_env_var`` class attribute on the config.
setup.py:78
install_requiresrequiresconfargs>=0.9, but this PR’s own test environments installconfargs >= 0.8(seeutest/requirements.txtandatest/requirements-run.txt). This mismatch can let CI run with 0.8 while released installs require 0.9 (or vice versa), causing version-dependent behavior differences (CLI parsing / error messages). Align the minimum confargs version across runtime + test requirements (likely>=0.8given the PR notes).
python_requires=">=3.10",
install_requires=[
# Declarative CLI parsing and configuration-file support. confargs
# requires Python 3.10+, which is why Robot Framework's minimum
# supported Python version is raised to 3.10 in this change.
"confargs>=0.9",
],
| except Exit as exit_signal: | ||
| # confargs raises ``Exit`` as a clean-exit signal (e.g. after | ||
| # ``--show-completion`` / ``--install-completion`` have already | ||
| # printed their output). It is deliberately *not* an | ||
| # ``ArgConfigError``, so translate it into Robot Framework's own | ||
| # clean-exit path with a silent, success return code rather than | ||
| # letting it be reported as a usage error. | ||
| raise Information("", status_rc=bool(exit_signal.code)) | ||
| except ArgConfigError as err: |
| def test_joined_and_cased_negation(self): | ||
| opts, _ = parser().parse_args(["--nostatusrc", "data.robot"]) | ||
| assert_equal(opts["statusrc"], False) | ||
| opts, _ = parser().parse_args(["--No-DryRun", "data.robot"]) | ||
| assert_equal(opts["dryrun"], False) |
Summary
Replaces the hand-rolled
getopt-based command-line parsing of therobotand
rebottools with the declarative confargslibrary, and adds configuration-file support (closes #5337).
Behaviour of the parsed options is preserved — the downstream
RobotSettings/RebotSettingsdefaulting is unchanged — and, thanks toconfargs' opt-in lenient name matching, the command-line syntax stays
backwards compatible too (case- and hyphen-insensitive long names,
unique-prefix abbreviation,
--no<flag>negation). See Compatibility below.What changed
robot.conf.argumentsArgConfigsubclassesRobotArgs/RebotArgs(shared base_CommonArgs) declaring every option as a plain, pass-through confargs option. This is the single source of truth for the CLI. Both enablecli_case_insensitive/cli_ignore_hyphens/cli_allow_abbrev.robot.utils.confargsparserConfargsParser— a thin adapter exposing the same surfaceApplicationexpects (name,version,parse_args(args) -> (opts, datasources)), raisingInformationfor--help/--versionandDataErroron failure.robot.utils.applicationApplication.__init__gained an optionalconfig=param; when set it builds aConfargsParser, otherwise the legacyArgumentParser.robot.run/robot.rebotconfig=RobotArgs/config=RebotArgs.robot.utils.argumentparserlibdoc/testdockeep using the legacy parser.setup.pyconfargs>=0.7dependency.No dynamic class generation and no pre-processing/normalisation of argv on the
Robot Framework side — options are handed to confargs directly, mirroring the
tests/robot_cli.pyreference fixture. The legacy leniencies that RF users rely on (case/hyphen
insensitivity, joined negation, unique-prefix abbreviation) are restored via
confargs' own opt-in features rather than a robot-side canonicalisation layer.
Configuration file support (#5337)
Sources are merged with precedence:
[tool.robot]/[tool.rebot]table, discovered bywalking up from the working directory (stopping at
.git) plus the per-userconfig dir.
config_names=robot.toml,pyproject.toml(rebot also readsrebot.toml).--config PATH,--no-config,--profile NAME,--ignore-git. Named profiles ([tool.robot.profiles.ci])are supported out of the box.
ROBOT_OPTIONS/REBOT_OPTIONSare still honoured (read by confargs).robot tests/now picks up that config;robot --name Other tests/overridesit;
robot --no-config tests/ignores it.Config keys use the exact option name only (e.g.
variablefile,outputdir) — the case/hyphen leniency below applies to the command line, notto config files.
Supported option forms (with examples)
--name My Suite=value--name=My Suite--Name,--OutputDir--variable-file(=--variablefile)--variab(=--variablefile, if unambiguous)-N My Suite-NMy Suite-i tag1 -i tag2/--include tag1 --include tag2--dryrun,-X-XT(=--exitonfailure --timestampoutputs)--no-dryrun,--nostatusrc,--no-statusrc-- --not-an-option.robot-A args.txt,--argumentfile STDINShort options stay case-sensitive by design (matching the legacy parser):
-Vis--variablefileand-vis--variable; leniency applies to longnames only.
Compatibility with the legacy parser
Restored via confargs' opt-in features, so these keep working:
--Name,--OutputDircli_case_insensitive--tag-stat-link,--pre-run-modifiercli_ignore_hyphens--outp,--variab,--removekcli_allow_abbrev(confargs 0.7; ambiguous prefixes raise)--no<flag>negation (no dash)--nostatusrc--nostatusrcand--no-statusrc)--nostat(=--nostatusrc)cli_allow_abbrev+cli_ignore_hyphens, confargs 0.8)Still intentionally dropped (the only user-visible behaviour changes):
-?-?-h/--helpEverything else — option names, short flags (incl.
-.for--dotted),value/multi/flag semantics, defaults,
--helptext (the full USAGE is passedthrough verbatim, footer included),
--version, exit codes, data-sourceglobbing,
ROBOT_OPTIONS— is unchanged.Tests
utest/utils/test_argumentparser.pykept pristine (libdoc/testdocpath unchanged).
utest/utils/test_confargsparser.py(20 tests) covers value/multi/flagparsing,
=syntax, shorts,--no-negation, case-/hyphen-insensitive longnames, joined/cased negation (
--nostatusrc,--No-DryRun), case-sensitiveshorts,
--help/--version→Information, arg-limit errors,unknown-option →
DataError, internal keys not leaking, validator hook, andrebot-specific options.
atest/robot/cli, 460 tests) run against confargs 0.8:456 pass, 4 fail. All 4 remaining failures are environmental, not
caused by this change (
Console.Non Ascii.*andArgument File.Argument file with non-ASCII characters— Windows-console mojibake on a non-UTF-8 codepage;they pass on the Linux CI runners). The argument-file BOM,
# expandvars:pragma and abbreviated-joined-negation failures were all fixed by confargs
0.8 (MarketSquare/confargs#40), and the obsolete
Shortening --argumentfile is not possibleatest was removed.robot/rebot--version/--help, real suiteexecution, config-only vs CLI-override vs
--no-config,-Aargument files.ruff+isort+blackapplied to changed files.TODO / open questions
python_requires, the trove classifiers and the CI matrices (unit +acceptance) were updated to drop 3.8/3.9. confargs was also added to
utest/requirements.txtandatest/requirements-run.txt(used by the testworkflows, which run from source without
pip install .). This floor bump isa required consequence of adopting confargs and needs project sign-off.
restored via confargs' opt-in
cli_case_insensitive/cli_ignore_hyphens(0.6) and
cli_allow_abbrev(0.7) toggles, so the CLI stays backwardscompatible without any robot-side canonicalisation.
ArgumentParser. Migrate themtoo, or keep the two parsers side by side long-term?
utf-8-sig, so a leading UTF-8 BOM is stripped(MarketSquare/confargs#40).
# expandvars:pragma is now supported by confargs 0.8(
${VAR}/${VAR=default}expansion with RF-compatible error messages whenthe pragma is present).
--nostatfor--nostatusrc) now works:confargs 0.8 composes
cli_allow_abbrevwith the joined--no<flag>form.Shortening --argumentfile is not possibleatest removed — abbreviationmakes shortening work, so the test's premise is obsolete.
robot.run()/rebot()) keyword names are unchanged(e.g.
outputdir,loglevel,variablefile), matching the user guide — areadability rename was considered but rejected to avoid breaking that API.
conventions / documented search paths.