Skip to content

refactor: config access is typed, not defensive#353

Merged
stanlrt merged 15 commits into
mainfrom
352-config-access-honesty
Jul 7, 2026
Merged

refactor: config access is typed, not defensive#353
stanlrt merged 15 commits into
mainfrom
352-config-access-honesty

Conversation

@stanlrt

@stanlrt stanlrt commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #352. Kills the defensive getattr(config|backend, "field", default) epidemic in favor of direct typed attribute access, so a missing AppConfig/ModelBackend field fails loud instead of returning a silent default. The type system becomes load-bearing.

Foundation

  • make_app_config now returns a real struct-mode OmegaConf DictConfig (was a bare SimpleNamespace) — the same shape Hydra hands prod, so direct reads are genuinely exercised.
  • Added make_fake_backend + ModelBackend.device on the ABC.

Sweep (~56 prod sites across 9 modules) — getattr(config, "x", d)config.x; deficient SimpleNamespace test fakes promoted to the real builders. Judgment sites handled: deleted the dead config.run read; backend.device via the ABC; excluded dynamic reflection (getattr(obj, variable)); preserved None-fallbacks on genuinely-optional fields (config.reporting, config.model.source, polymorphic num_classes).

Typing reconcilecheck_backend_compat(backend: ModelBackend | None) across base + both adapter Protocols + overrides + test doubles; the no-backend gate stays backend.provides if backend is not None else frozenset() (an absent backend provides nothing).

Guardrails — a CI step in the Quality Gate bans the defensive pattern in src/**; a new ADR record documents the policy.

Review

Built via subagent-driven-development: 11 tasks (foundation sequential, 7 parallel module sweeps, guard + ADR), plus a cross-task typing reconcile. Final whole-branch correctness review (found + fixed one Critical: num_classes crash on binary/multilabel metrics configs, now regression-tested) and a whole-branch refactor review (clean, zero Refactor-Critical).

uv run pyright src/raitap = 0 real errors; strict Sphinx build green.

Checklist

  • CI — Required checks are green
  • Breaking changes — Not user-breaking: public raitap.run and the config schema are unchanged; internal check_backend_compat/make_app_config signatures changed (pre-1.0, no shims).
  • Contributor guide — I've read Pull requests and commit messages before requesting review.

Optional

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 7, 2026 00:42
Comment thread src/raitap/data/tests/test_text_metadata.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes config/backend access across RAITAP by removing defensive getattr(..., default) patterns in favor of direct, typed attribute access on the structured AppConfig schema and ModelBackend ABC, so missing fields fail loudly rather than silently defaulting.

Changes:

  • Reworked test config/backends to use schema-faithful builders (make_app_config returning structured DictConfig; new make_fake_backend) and updated many tests accordingly.
  • Swept production modules to read config/backend fields directly (e.g., config.transparency, backend.provides, config.data.forward_batch_size) and tightened adapter/backend typing (e.g., backend: ModelBackend | None).
  • Added guardrails: an ADR documenting the policy and a CI “Quality Gate” step that rejects defensive getattr on config/backend in src/**.

Reviewed changes

Copilot reviewed 60 out of 60 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/raitap/transparency/tests/test_factory.py Ensures test configs include schema-defaulted data to support direct reads.
src/raitap/transparency/tests/test_e2e_integration.py Adds schema-defaulted data in e2e config builders.
src/raitap/transparency/tests/test_assess_transparency_detection.py Switches to schema ModelConfig to avoid missing-key crashes.
src/raitap/transparency/tests/e2e_case_matrix.py Adds schema-defaulted data in matrix configs.
src/raitap/transparency/phase.py Replaces defensive config reads with direct schema access.
src/raitap/transparency/explainers/tests/test_base_explainer.py Updates explainer tests to pass a typed backend via make_fake_backend().
src/raitap/transparency/explainers/base_explainer.py Types backend as `ModelBackend
src/raitap/transparency/contracts.py Tightens adapter Protocol typing for backend compatibility checks.
src/raitap/tests/test_capability_migration_guard.py Uses make_fake_backend to exercise capability gating without ad-hoc stubs.
src/raitap/tests/test_adapter_capability_gate.py Uses make_fake_backend to simplify capability-gate tests.
src/raitap/testing/tests/test_factories.py Adds tests asserting make_app_config structness/defaults + typed fake backend behavior.
src/raitap/testing/factories.py Replaces SimpleNamespace config fake with structured DictConfig; introduces make_fake_backend.
src/raitap/testing/init.py Exposes make_fake_backend in testing public API.
src/raitap/task_families/tests/test_classification.py Adds regression coverage for binary metrics configs and num_classes access.
src/raitap/task_families/tests/test_classification_forward_mask.py Switches forward-pass test config to make_app_config.
src/raitap/task_families/classification.py Adjusts num_classes access to avoid config-missing crashes across metrics variants.
src/raitap/robustness/tests/test_marabou_parity.py Updates backend stubs to implement ModelBackend contract.
src/raitap/robustness/tests/test_e2e_robustness_matrix.py Aligns robustness matrix configs with new direct config reads (explicit attrs).
src/raitap/robustness/tests/test_e2e_real_data.py Updates robustness-only pipeline test to use real AppConfig where needed.
src/raitap/robustness/tests/test_e2e_marabou_acas_xu.py Updates ONNX backend stubs to satisfy ModelBackend ABC.
src/raitap/robustness/phase.py Replaces defensive robustness config reads with direct schema access.
src/raitap/robustness/factory.py Uses direct config.experiment_name reads.
src/raitap/robustness/contracts.py Tightens assessor Protocol typing to accept `ModelBackend
src/raitap/robustness/assessors/tests/test_torchattacks_assessor.py Uses make_fake_backend instead of bespoke stubs.
src/raitap/robustness/assessors/tests/test_marabou_assessor.py Updates backend stubs to implement ModelBackend; uses make_fake_backend where appropriate.
src/raitap/robustness/assessors/tests/test_foolbox_assessor.py Uses make_fake_backend instead of bespoke stubs.
src/raitap/robustness/assessors/tests/test_auto_lirpa_assessor.py Updates backend stubs to implement ModelBackend contract.
src/raitap/robustness/assessors/marabou_assessor.py Types backend compat hook as `ModelBackend
src/raitap/robustness/assessors/base_assessor.py Uses typed backend and direct _prepare_inputs call.
src/raitap/robustness/assessors/auto_lirpa_assessor.py Types backend compat hook as `ModelBackend
src/raitap/reporting/tests/test_builder.py Uses make_app_config and attaches Hydra runtime node outside struct schema.
src/raitap/reporting/pdf_reporter.py Removes defensive reporting filename/model reads; asserts reporting is enabled.
src/raitap/reporting/hydra_callback.py Uses direct config.reporting access.
src/raitap/reporting/html_reporter.py Removes defensive metadata reads; asserts reporting is enabled.
src/raitap/reporting/factory.py Uses direct config.reporting access to gate reporting.
src/raitap/reporting/builder.py Uses direct schema reads for report metadata + manifest naming.
src/raitap/pipeline/ui.py Uses direct schema reads for summary config blocks.
src/raitap/pipeline/tests/test_forward_output_kind.py Replaces bespoke backends/configs with make_fake_backend/make_app_config.
src/raitap/pipeline/phases/tests/test_forward_pass.py Adjusts config stub to match new batch-size resolution path.
src/raitap/pipeline/phases/input_metadata.py Uses direct config.data.input_metadata.
src/raitap/pipeline/phases/forward_pass.py Resolves batch size from schema data.forward_batch_size; reads backend.provides directly.
src/raitap/pipeline/orchestrator.py Uses direct schema reads for seed/tracking/reporting gating.
src/raitap/models/torch_backend.py Narrows device type to non-optional for TorchBackend.
src/raitap/models/tests/test_tree_backend.py Uses make_fake_backend instead of bespoke stubs.
src/raitap/models/tests/test_models.py Switches to real schema dataclasses for config shaping.
src/raitap/models/tests/test_model_class.py Switches model config test stub to make_app_config.
src/raitap/models/model.py Uses direct schema reads for model source/hardware/tokenizer/task_kind/data metadata.
src/raitap/models/base_backend.py Adds device attribute on ModelBackend ABC to support direct reads.
src/raitap/metrics/factory.py Uses direct schema reads for metrics gating.
src/raitap/data/tests/test_text_metadata.py Adjusts config fakes to provide required schema-like data fields.
src/raitap/data/tests/test_metadata.py Adjusts config fakes to provide required schema-like data fields.
src/raitap/data/tests/test_detection_ragged.py Ensures test config provides model.class_names for direct reads.
src/raitap/data/tests/test_data.py Introduces helper to build minimal real AppConfig for data tests.
src/raitap/data/tests/test_data_class.py Ensures test config provides model.class_names for direct reads.
src/raitap/data/metadata.py Tightens signature to config: AppConfig and reads config.data fields directly.
src/raitap/data/data.py Removes defensive cfg.model access and unconditionally uses schema cfg.model.
src/raitap/configs/adapter_factory.py Uses direct schema reads for model/data preprocessing resolution.
src/raitap/_adapters.py Types backend compat to `ModelBackend
docs/contributor/adr.md Documents the “typed, not defensive” config access policy.
.github/workflows/code-quality.yaml Adds CI guard banning defensive getattr on config/backend in src/** (excluding tests).

Comment thread src/raitap/transparency/contracts.py
Comment thread src/raitap/robustness/contracts.py
Comment thread src/raitap/data/metadata.py
Comment thread src/raitap/transparency/explainers/base_explainer.py
Comment thread src/raitap/_adapters.py
Comment thread src/raitap/robustness/assessors/auto_lirpa_assessor.py
Comment thread src/raitap/robustness/assessors/marabou_assessor.py
Comment thread src/raitap/task_families/classification.py Outdated
Comment thread src/raitap/tests/test_memory_leaks.py
@stanlrt

stanlrt commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Automated-review triage

All CI green. The bot comments are false positives — no code change needed:

ModelBackend imported under TYPE_CHECKING (Copilot, 7 files) — the module uses from __future__ import annotations, so annotations like check_backend_compat(self, backend: ModelBackend | None) and the adapter Protocol signatures are postponed (strings), never evaluated at runtime. typing.get_type_hints() in this codebase resolves only the *AlgorithmSpec / semantics annotations (see the existing # noqa: TC001 sites in robustness/semantics.py, transparency/semantics.py, data/*/base.py) — it does not introspect check_backend_compat or the Protocols. So the TYPE_CHECKING import is correct; moving ModelBackend to a runtime import would risk import cycles for no benefit.

Unused AppConfig (github-code-quality, 2 test files) — the name is used in cast("AppConfig", ...) string annotations under TYPE_CHECKING; it's required for pyright to resolve the cast (ruff correctly does not flag it).

config.metrics dataclass-instance path (Copilot, classification.py) — handled: that line intentionally uses getattr(config.metrics, "num_classes", None), which reads correctly across both the OmegaConf DictConfig and the AppConfig(metrics=MulticlassClassificationMetricsConfig(...)) dataclass-instance shapes. num_classes is a genuinely polymorphic field (only on the multiclass variant), not the typed-config defensiveness this PR removes.

@stanlrt stanlrt merged commit f979b4c into main Jul 7, 2026
18 checks passed
@stanlrt stanlrt deleted the 352-config-access-honesty branch July 7, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: config access is typed, not defensive (kill getattr epidemic)

2 participants