Skip to content

topmark.config.overrides

topmark / config / overrides

Apply internal config-like override intent to a resolved MutableConfig draft.

This module sits after layered config discovery/resolution. Its job is to apply the highest-precedence config-like override layer produced by CLI or API orchestration without reimplementing config-file discovery.

PolicyOverrides and ConfigOverrides are internal typed value objects. They are not part of TopMark's stable public Python API. Public callers should pass plain mapping-based configuration and policy inputs to topmark.api entry points such as topmark.api.check(), topmark.api.strip(), and topmark.api.probe().

Execution-only runtime intent such as apply mode, stdin routing, output target, file write strategy, pruning, and timestamps is intentionally out of scope and must be handled separately via RunOptions.

Override-application diagnostics belong to the merged-config validation stage. Flattening is performed only at reporting and output boundaries.

PolicyOverrides dataclass

PolicyOverrides(
    *,
    header_mutation_mode=None,
    allow_header_in_empty_files=None,
    empty_insert_mode=None,
    render_empty_header_when_no_fields=None,
    allow_reflow=None,
    allow_content_probe=None,
)

Internal typed overrides for global or per-file-type policy values.

None means "no override provided" for the corresponding policy field. These values are produced by CLI/API orchestration after public mapping inputs have been normalized. They are intentionally not exported from topmark.api and should not be treated as a stable integration surface.

ConfigOverrides dataclass

ConfigOverrides(
    *,
    config_origin=SyntheticConfigSource(
        label=CLI_OVERRIDE_STR
    ),
    config_base=(lambda: Path.cwd().resolve())(),
    strict=None,
    policy=PolicyOverrides(),
    policy_by_type=(lambda: {})(),
    files=None,
    files_from=None,
    include_from=None,
    exclude_from=None,
    include_patterns=None,
    exclude_patterns=None,
    include_file_types=None,
    exclude_file_types=None,
    header_fields=None,
    field_values=None,
    align_fields=None,
    relative_to=None,
)

Internal highest-precedence config overrides from CLI or API orchestration.

None means "no override provided". Empty collections are meaningful and should therefore be preserved.

This dataclass is an internal bridge between user-facing inputs and mutable config drafts. Public API callers should provide plain mappings to topmark.api functions instead of constructing ConfigOverrides directly.

Attributes:

Name Type Description
config_origin Path | SyntheticConfigSource

Real config path or synthetic config source marker used for provenance.

config_base Path

real filesystem base for relative patterns and sources

strict bool | None

Config-loading strictness override. This remains config-like override intent and is separate from execution-only runtime options. It currently governs strict evaluation of staged config-loading validation.

policy PolicyOverrides

Internal global policy override values applied after discovery.

policy_by_type dict[str, PolicyOverrides]

Internal per-file-type policy override values applied after discovery.

files list[str] | None

List of files to process.

files_from list[str] | None

Paths to files that list newline-delimited candidate file paths to add before filtering.

include_from list[str] | None

Files containing include patterns.

exclude_from list[str] | None

Files containing exclude patterns.

include_patterns list[str] | None

Glob patterns to include.

exclude_patterns list[str] | None

Glob patterns to exclude.

include_file_types list[str] | None

Whitelist of file type identifiers to restrict file discovery.

exclude_file_types list[str] | None

Blacklist of file type identifiers to exclude from file discovery.

header_fields list[str] | None

List of header fields from the [header] section.

field_values dict[str, str] | None

Mapping of field names to their string values from [fields].

align_fields bool | None

Whether to align fields, from [formatting].

relative_to str | None

Base path used only for header metadata (e.g., file_relpath). Note: Glob expansion and filtering are resolved relative to their declaring source (config file dir or CWD for CLI), not relative_to.

apply_config_overrides

apply_config_overrides(config, overrides)

Apply internal CLI/API override intent to an existing mutable config draft.

This helper updates an already-resolved MutableConfig with the final highest-precedence override layer. It intentionally does not handle config discovery concerns such as --no-config or --config; those belong to topmark.toml.resolution and topmark.config.resolution.

Parameters:

Name Type Description Default
config MutableConfig

Mutable config draft to mutate in place.

required
overrides ConfigOverrides

Internal structured override values produced by CLI/API orchestration.

required

Returns:

Type Description
MutableConfig

The same MutableConfig instance

MutableConfig

after in-place mutation.

Notes
  • Path-to-file options (--include-from, --exclude-from, --files-from) are normalized against overrides.config_base, which defaults to the invocation working directory but may differ for API callers.
  • relative_to influences only header metadata generation. It does not change how config-declared glob sources are interpreted.
  • Provenance information is appended to overrides.config_origin so downstream views can show that a highest-precedence override layer was applied.
  • ConfigOverrides and PolicyOverrides are internal bridge types, not stable public API inputs. Public callers should use mapping-based arguments accepted by topmark.api.
  • Execution-only runtime intent (apply mode, STDIN routing, output target, file write strategy, pruning, timestamps) is out of scope here and must be handled separately via RunOptions.
  • Override-application diagnostics are recorded in the merged-config validation stage. Flattening is now performed only at reporting and output boundaries.
Source code in src/topmark/config/overrides.py
def apply_config_overrides(
    config: MutableConfig,
    overrides: ConfigOverrides,
) -> MutableConfig:
    """Apply internal CLI/API override intent to an existing mutable config draft.

    This helper updates an already-resolved [`MutableConfig`][topmark.config.model.MutableConfig]
    with the final highest-precedence override layer. It intentionally does **not** handle
    config discovery concerns such as `--no-config` or `--config`; those belong to
    [`topmark.toml.resolution`][topmark.toml.resolution] and
    [`topmark.config.resolution`][topmark.config.resolution].

    Args:
        config: Mutable config draft to mutate in place.
        overrides: Internal structured override values produced by CLI/API orchestration.

    Returns:
        The same [`MutableConfig`][topmark.config.model.MutableConfig] instance
        after in-place mutation.

    Notes:
        - Path-to-file options (`--include-from`, `--exclude-from`, `--files-from`) are normalized
          against `overrides.config_base`, which defaults to the invocation working directory
          but may differ for API callers.
        - `relative_to` influences only header metadata generation. It does not change how
          config-declared glob sources are interpreted.
        - Provenance information is appended to `overrides.config_origin` so downstream views
          can show that a highest-precedence override layer was applied.
        - [`ConfigOverrides`][topmark.config.overrides.ConfigOverrides] and
          [`PolicyOverrides`][topmark.config.overrides.PolicyOverrides] are internal bridge types,
          not stable public API inputs. Public callers should use mapping-based
          arguments accepted by `topmark.api`.
        - Execution-only runtime intent (apply mode, STDIN routing, output target, file write
          strategy, pruning, timestamps) is out of scope here and must be handled separately via
          [`RunOptions`][topmark.runtime.model.RunOptions].
        - Override-application diagnostics are recorded in the merged-config
          validation stage. Flattening is now performed only at reporting and
          output boundaries.
    """
    merged_diagnostics: MutableDiagnosticLog = config.validation_logs.merged_config

    # Record that a highest-precedence CLI/API override layer was applied.
    # This is provenance-only; explicit `--config` files are merged earlier by
    # the resolution layer.
    if config.config_files:
        config.config_files.append(overrides.config_origin)
    else:
        config.config_files = [overrides.config_origin]

    # Note: CLI config paths are already merged elsewhere
    # so we don't extend config.config_files here.

    # policy always exists via default_factory
    _apply_policy_overrides(config.policy, overrides.policy)

    for ft, policy_override in overrides.policy_by_type.items():
        dst: MutablePolicy = config.policy_by_type.get(ft) or MutablePolicy()
        _apply_policy_overrides(dst, policy_override)
        config.policy_by_type[ft] = dst

    # Explicit file inputs replace any previously resolved file list.
    if overrides.files is not None:
        raw_files: list[str] = overrides.files
        empties: list[str] = [s for s in raw_files if not s]
        if empties:
            merged_diagnostics.add_warning(
                f"Ignoring empty string entries in override files: {empties!r}"
            )
        # Keep only the non-empty entries
        config.files = [s for s in raw_files if s]

    # CLI/API override paths and pattern groups are interpreted against the override base.
    base_dir: Path = overrides.config_base.resolve()

    if overrides.include_patterns is not None:
        include_patterns: tuple[str, ...] = tuple(s for s in overrides.include_patterns if s)
        if include_patterns:
            config.include_pattern_groups = [
                PatternGroup(
                    patterns=include_patterns,
                    base=base_dir,
                )
            ]
        else:
            config.include_pattern_groups = []

    if overrides.exclude_patterns is not None:
        exclude_patterns: tuple[str, ...] = tuple(s for s in overrides.exclude_patterns if s)
        if exclude_patterns:
            config.exclude_pattern_groups = [
                PatternGroup(
                    patterns=exclude_patterns,
                    base=base_dir,
                )
            ]
        else:
            config.exclude_pattern_groups = []

    # Path-to-file options: include_from, exclude_from, files_from (validate list[str])
    # Strategy: replace-or-clear
    if overrides.include_from is not None:
        config.include_from = []
        vals: list[str] = [s for s in overrides.include_from if s]
        extend_pattern_sources(
            vals,
            dst=config.include_from,
            mk=pattern_source_from_cwd,
            kind="override include_from",
            base=base_dir,
        )
    if overrides.exclude_from is not None:
        config.exclude_from = []
        vals = [s for s in overrides.exclude_from if s]
        extend_pattern_sources(
            vals,
            dst=config.exclude_from,
            mk=pattern_source_from_cwd,
            kind="override exclude_from",
            base=base_dir,
        )

    if overrides.files_from is not None:
        config.files_from = []
        vals = [s for s in overrides.files_from if s]
        extend_pattern_sources(
            vals,
            dst=config.files_from,
            mk=pattern_source_from_cwd,
            kind="override files_from",
            base=base_dir,
        )

    # Header-building overrides apply only when explicitly provided.

    # relative_to: string or None, only override if non-empty string (not just whitespace)
    if overrides.relative_to and (rel_str := overrides.relative_to.strip()):
        config.relative_to_raw = rel_str
        rel_path = Path(rel_str)
        config.relative_to = (
            rel_path.resolve() if rel_path.is_absolute() else (base_dir / rel_path).resolve()
        )

    # align_fields: checked bool
    if overrides.align_fields is not None:
        config.align_fields = overrides.align_fields

    # Absent override values intentionally preserve whatever came from layered
    # config discovery / TOML resolution.

    # include_file_types / exclude_file_types: set of strings, only override if present
    if overrides.include_file_types is not None:
        # Only override when the user actually passes a value; `()` clears the property
        filtered: list[str] = [s for s in overrides.include_file_types if s]  # drop empty strings
        deduped: set[str] = set(filtered)
        config.include_file_types = deduped
        dup_count: int = len(filtered) - len(deduped)
        if dup_count:
            merged_diagnostics.add_info(
                f"Ignored {dup_count} duplicate values for override include_file_types"
            )

    if overrides.exclude_file_types is not None:
        # Only override when the user actually passes a value; `()` clears the property
        filtered: list[str] = [s for s in overrides.exclude_file_types if s]  # drop empty strings
        deduped: set[str] = set(filtered)
        config.exclude_file_types = deduped
        dup_count: int = len(filtered) - len(deduped)
        if dup_count:
            merged_diagnostics.add_info(
                f"Ignored {dup_count} duplicate values for override exclude_file_types"
            )

    # NOTE: header_fields and field_values still bypass any provenance-aware normalization
    if overrides.header_fields is not None:
        config.header_fields = overrides.header_fields
    if overrides.field_values is not None:
        config.field_values = overrides.field_values

    logger.debug("Patched MutableConfig: %s", config)
    logger.info("Applied argument mapping overrides to MutableConfig")
    logger.debug("Finalized override application for files=%s", config.files)

    return config