Skip to content

refactor: extract _collect_fixtures seam from expose#13

Merged
lesnik512 merged 1 commit into
mainfrom
refactor/collect-fixtures-seam
Jun 26, 2026
Merged

refactor: extract _collect_fixtures seam from expose#13
lesnik512 merged 1 commit into
mainfrom
refactor/collect-fixtures-seam

Conversation

@lesnik512

Copy link
Copy Markdown
Member

What

Splits expose() into a pure decision and a thin installer.

A new private _collect_fixtures(*groups) -> dict[str, AbstractProvider] walks the groups, skips non-Provider attributes, detects cross-group name collisions, and returns a name -> provider mapping. expose() now resolves that mapping (surfacing any error) before touching the module, then installs each provider via the existing modern_di_fixture.

Why

expose() fused three jobs behind one side-effecting interface — discovery, collision detection, and setattr-onto-module. The testable core ("which names, any conflicts?") was only observable past the interface, so the tests had to build throwaway modules and probe with hasattr. And because installation mutates module globals, two configurations couldn't share a module — which is exactly why the suite had four near-identical test_expose_*.py files.

Drawing the seam at the decision makes that core a value you can assert on directly.

Behaviour change: atomic collision

The ValueError is now raised before any setattr, so a failed expose() leaves the target module untouched (previously the first group's fixtures were already installed when the collision fired). Locked in with a test.

Tests: 4 → 3 expose files

  • test_collect_fixtures.py (new) — skip / collision / no-groups / mapping rules tested directly against the seam's return value. No throwaway modules, no hasattr probing.
  • test_expose.py — integration only: app-scope resolution, multiple groups (folded in), the module= seam, the exec RuntimeError, and the new atomicity guarantee.
  • test_expose_request_scope.py — kept separate, with a comment explaining the request_widget name collision that forces it.
  • test_modern_di_fixture.py — untouched.

Contract

  • Public surface unchanged — still two public symbols; _collect_fixtures is private.
  • Error messages verbatim.
  • just lint (ruff + ty) clean; just test-ci green at 100% coverage.

🤖 Generated with Claude Code

Split expose() into a pure decision and a thin installer. The new private
_collect_fixtures(*groups) walks the groups, skips non-Providers, detects
cross-group name collisions, and returns a name->provider mapping. expose()
now resolves that mapping (surfacing any error) before touching the module,
then installs via modern_di_fixture.

Guarantee added: collision is atomic. The ValueError is raised before any
setattr, so a failed expose() leaves the target module untouched.

The decision is now testable through a return value instead of module side
effects, so the skip/collision/no-groups tests move to a new pure file and
the four test_expose_* files collapse to three. Public surface unchanged
(still two public symbols); error messages verbatim; 100% coverage holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lesnik512 lesnik512 merged commit 1e35d70 into main Jun 26, 2026
6 checks passed
@lesnik512 lesnik512 deleted the refactor/collect-fixtures-seam branch June 26, 2026 06:04
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.

1 participant