refactor(config): typed Config model with consistent CLI/env/file/SSL handling#752
refactor(config): typed Config model with consistent CLI/env/file/SSL handling#752TheKevJames wants to merge 10 commits into
Conversation
Introduce a typed home for coveralls-python configuration, splitting fields into API payload fields (emitted via to_payload) and client-behaviour settings that must never enter the submitted JSON. Absorbs the timeout validation and (connect, read) derivation that previously lived as ad-hoc helpers on the Coveralls class. Not yet wired into Coveralls/CLI; subsequent commits move the resolution logic onto this type.
Port the CI-service detection, COVERALLS_* env vars, and .coveralls.yml loading into normalized source functions that each return a uniform partial config, merged by resolve() in the documented precedence order into a typed Config. - Replace the per-service positional (name, job, number, pr) tuples with partial dicts and a shared _pr_from_path helper for URL-style PR parsing. - token_required is AND-ed across sources: any source may waive it (TravisCI, debug/output) but none may re-impose it. - Unknown keys from the config file are dropped with a warning, giving a clean migration path for the upcoming coveralls_host/config_file YAML renames. Not yet wired into Coveralls; that swap lands next.
…o payload Coveralls now resolves configuration into a typed Config via resolve() instead of building an untyped dict, and create_data() emits only Config.to_payload() (the API payload fields). This fixes base_dir/src_dir/rcfile and the timeout family leaking into the submitted JSON. - Delete the load_config_from_* loaders, _normalize_timeouts, _request_timeout, _coveralls_host and _token_required; all now live on Config/resolve. - ensure_token, submit_report, parallel_finish and get_coverage read typed fields; the 422 branch uses config.service_name (never None post-resolve), removing the KeyError sharp edge. - Rename the coverage rcfile constructor kwarg config_file -> rcfile. - Trim configuration_test.py to Coveralls integration + ensure_token; the per-CI/per-timeout resolution is covered by resolve_test.py/config_test.py.
The call-time os.environ reads for these were removed when Coveralls moved onto Config; add an override-path test to lock in that host and skip_ssl_verify are settable through any channel, not only COVERALLS_HOST/COVERALLS_SKIP_SSL_VERIFY.
…d aliases Collapse the duplicated coveralls()/debug() commands into one command with a --debug flag, removing the copy-pasted option set (incl. the duplicated --version callback and timeout flags). - Add first-class --host, --skip-ssl-verify and --parallel flags. - Rename --service/--basedir/--srcdir to --service-name/--base-dir/--src-dir, following the field/env/flag naming convention; keep the old spellings as hidden aliases that log a deprecation warning. - Fully type cli.py; build Config overrides via a helper that only forwards explicitly-set boolean flags.
Update the configuration and troubleshooting docs for the single-command CLI, the new --host/--skip-ssl-verify/--parallel flags, and the renamed --service-name/--base-dir/--src-dir flags (with deprecation notes). Document the .coveralls.yml key renames and clean the leaked config_file/base_dir keys out of the example report fixture. BREAKING CHANGE: configuration now uses one consistent name per setting across CLI flags, COVERALLS_* env vars and .coveralls.yml. The 'coveralls_host' and 'config_file' config-file keys are renamed to 'host' and 'rcfile'; the --service/--basedir/--srcdir flags are renamed to --service-name/--base-dir/ --src-dir (old spellings still work but are deprecated); the 'debug' subcommand becomes a --debug flag; and the Coveralls(config_file=...) constructor kwarg is renamed to rcfile. Client settings (base_dir, src_dir, rcfile, host, skip_ssl_verify, timeouts) are no longer leaked into the submitted payload.
…a warning Mirror the deprecated CLI flag aliases for config keys: translate the renamed coveralls_host -> host and config_file -> rcfile keys to their canonical names (warning on use) instead of dropping them. The same rule applies to both the config file and Coveralls() keyword arguments, since both are dicts of user-supplied config keys. This restores backwards compatibility for existing .coveralls.yml files and for Coveralls(config_file=...) callers, removing those breaking changes. An explicit canonical key in the same source still takes precedence.
Update: backwards-compat for renamed config keysApplied the same deprecated-alias-with-warning treatment (used for CLI flags) to
Re: "does this make it non-breaking?" — verified, partiallyKey aliasing removes the common user-facing breaks. Remaining breaks after
So it is not strictly non-breaking, but the realistically-impactful surfaces Open question: the
|
Bring back 'coveralls debug' as a subcommand (the documented troubleshooting entry point) instead of a --debug flag, while avoiding the copy-pasted typer.Option blocks that CODE_REVIEW #4 called out. Each option spec is defined once as a shared Annotated type alias and referenced by both the default command and the debug subcommand, which are thin wrappers over a common _dispatch(). Changing a flag name or help string now updates both entry points automatically.
Update:
|
service_name is now a plain str defaulting to 'coveralls-python' rather than
str | None resolved to that value later, so submit_report()'s
service_name.startswith('github') check can never hit None. The default literal
lives on the Config field (DEFAULT_SERVICE_NAME); _detect_ci() returns None
when no CI is detected and lets that default apply.
The CLI always forwarded rcfile='.coveragerc' as a highest-precedence override, so an rcfile/config_file set in .coveralls.yml was silently ignored (while its deprecation warning still fired, implying it took effect). Default --rcfile to None and let resolve() drop it, matching how the other string flags already work, so the config-file value survives. coverage.py treats config_file='.coveragerc' and the auto-discovery default identically, so the no-config behaviour is unchanged.
|
Good catch — reproduced and fixed. Root cause was exactly as described: Fix: I went with the "only forward when passed" option rather than just documenting, Regression coverage added:
|
Summary
Cleans up how coveralls-python handles configuration values (CLI flags, CI-system
environment detection,
COVERALLS_*env vars,.coveralls.yml, and our ownsettings such as disabling SSL verification). Introduces a single typed
Configmodel and a
resolve()pipeline, replacing the untypedself.configdict that wasscattered across
api.py.Motivation
The old design merged three unrelated kinds of value into one untyped dict and then
dumped the whole dict into the submitted JSON. This leaked client-only settings
(
base_dir,src_dir,rcfile, and the newly-added timeout family) into thepayload sent to coveralls.io. State was also spread across ad-hoc attributes
(
_coveralls_host,_token_required), call-timeos.environreads (SSL/host), andpositional per-service tuples, and the CLI duplicated every option across two
commands.
What changed
New
coveralls/configuration.pyConfigdataclass splitting API payload fields from client-behavioursettings, with
to_payload()as the enforced boundary.resolve()in the documented precedenceorder (CI env →
COVERALLS_*→ config file → explicit overrides). Positionalper-service tuples and duplicated PR-parsing removed; a shared
_pr_from_pathhelper handles URL-style PR numbers.
token_requiredis AND-ed across sources (any source may waive it, none mayre-impose it). Unknown config-file keys are dropped with a warning.
Fixes
_coveralls_host/_token_required/_normalize_timeouts/_request_timeoutand the call-time SSL/host env reads folded onto
Config.debugsubcommand collapsed into a--debugflag; the copy-pasted optionset (incl.
--versionand timeout flags) defined once.api.pyandcli.pyare now typed (mypy passes).service_nameKeyError sharp edge,re.IGNORECASEon a digit regex, adocstring typo.
Behaviour changes (consistency-driven)
One naming convention for every setting:
foo_bar↔--foo-bar↔COVERALLS_FOO_BAR↔foo_bar(YAML).--host,--skip-ssl-verify,--parallel.--service/--basedir/--srcdir→--service-name/--base-dir/--src-dir; old spellings kept as hidden aliasesthat emit a deprecation warning.
coveralls_host→host,config_file→rcfile(unknown keys warn).Coveralls(config_file=...)constructor kwarg →rcfile.Migration notes are documented in
docs/usage/configuration.rst; theBREAKING CHANGE:footers will surface in the git-cliff changelog at release.Testing
tests/api/config_test.py(Config unit tests, incl. leak regression guards)and
tests/api/resolve_test.py(per-source resolution + precedence).configuration_test.pytrimmed to Coveralls integration +ensure_token.pre-commit run -aclean.Known follow-ups (out of scope)
--helpstill rendersCOMMAND [ARGS]...(Typer callback-only app is a group);left as-is to avoid touching the
main()/error-handling contract.--strictflip, exception__eq__removal, the misleading resubmissiontest, and lockfile reconciliation remain separate concerns.