Skip to content

refactor(providers): single scope-source field; explicit group-stamp opt-out - #425

Open
lesnik512 wants to merge 1 commit into
mainfrom
refactor/provider-scope-provenance
Open

refactor(providers): single scope-source field; explicit group-stamp opt-out#425
lesnik512 wants to merge 1 commit into
mainfrom
refactor/provider-scope-provenance

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Why

AbstractProvider carried four fields to answer one question — what scope, and may a
group still change it?

field role
scope effective value
_scope_defaulted was it chosen explicitly
_stamping_group which group already stamped it
_registered may a resolver have captured it (the #407 freeze)

_scope_defaulted and _stamping_group are two encodings of the same fact: where the
current scope came from. _scope_defaulted exists only because the constructor collapses
UNSET → Scope.APP on the first line, erasing why a provider is APP-scoped, which is
exactly what the precedence rule needs (explicit scope= > group default > APP).

Separately, Alias and _ContainerProvider opted out of group stamping by passing a
concrete scope=Scope.APP in order to leave _scope_defaulted False. That is a side
effect standing in for an intent, and alias.py needed two comment lines to explain the
trick.

Design

One provenance field, _scope_source, whose three states are the three ways a scope is
chosen:

  • None — nobody chose; scope is the Scope.APP fallback and a group may stamp it
  • str — that group's default stamped it (the name is what GroupScopeConflictError reports)
  • _EXPLICIT_SCOPE — the constructor's scope= argument; a group default is a silent no-op

_stamp_group_scope becomes a flat read of that one field instead of two booleans that
had to stay in sync:

source = self._scope_source
if not self._takes_group_scope or isinstance(source, _ExplicitScope):
    return
if source is not None:          # a group already claimed it
    ...conflict-if-different...
if self._registered and self.scope != scope:
    ...frozen...

_takes_group_scope: ClassVar[bool] is the second half: Alias and _ContainerProvider
set it False and say so where it is true of them. Alias now passes scope=types.UNSET,
which is honest — an alias chooses no scope, its effective scope derives from its source —
where the old Scope.APP was a placeholder doing double duty as the opt-out.

Net: one slot fewer, one predicate per branch, and the un-stampability of a derived-scope
provider is declared rather than arranged.

Behaviour is unchanged. Existing tests, including every case in the group-scope precedence
and freeze suites, pass untouched.

Non-goals

  • Not moving effective scope off the provider and into registration data. That would
    delete _registered, ProviderScopeFrozenError, and the freeze suite outright, but
    Group.svc.scope reflecting the group default before any container exists is documented
    (docs/providers/scopes.md) and asserted, and provider.scope is read in suggester.py,
    dependency_graph.py, integrations.py, and exceptions.py where no registry is in hand.
  • Not changing the precedence rule, the freeze, or any error message. No docs change:
    every user-facing statement in docs/providers/scopes.md still holds verbatim.
  • _takes_group_scope stays private. It is not a provider-extension seam.

Verification

  • just test-ci — 511 passed, 100.00% line coverage (the gate).
  • just lint-ci — ruff + ty + planning bundle checks all clean.
  • Two invariant tests added to tests/test_group.py, both written and confirmed passing
    before the refactor so they are real characterization, not tests fitted to the new code:
    • test_group_scope_alias_still_resolves_from_the_source_container — an Alias in a
      REQUEST group over an APP source still resolves from the APP container. The pre-existing
      test_group_scope_alias_keeps_derived_scope only asserted the stored attribute; this
      pins the consequence.
    • test_group_scope_does_not_stamp_the_container_providercontainer_provider is
      public, so a group body may list it; a group default must not move the one shared
      singleton.

…opt-out

Replace _scope_defaulted + _stamping_group with one _scope_source field, and
give Alias and the container provider a declarative _takes_group_scope = False
instead of relying on a placeholder explicit scope to dodge stamping.
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