{{ message }}
refactor: drop frozen=True from instruments; sentinel for FastAPIConfig.application#101
Merged
Merged
Conversation
…ig.application
REF-6: LoggingInstrument and OpenTelemetryInstrument cached mutable
runtime state (_logger_factory, _tracer_provider) via
object.__setattr__ workarounds because the instruments were declared
frozen=True. The frozen claim was partly false — those two fields
mutated freely under the hood.
Python's dataclass rules forbid surgically dropping frozen=True from
a subclass while the parent remains frozen (TypeError at class
definition). The fix cascades through BaseInstrument and all 22
instrument subclasses: drop frozen=True from each. Configs stay
frozen — only instruments lose immutability. The 4 object.__setattr__
call sites in LoggingInstrument and OpenTelemetryInstrument
(bootstrap-cache + teardown-reset for each) become plain self._x = ...
assignments.
LOW-4: FastAPIConfig.application declared default=None with a
# ty: ignore[invalid-assignment] because the field is typed
fastapi.FastAPI (non-Optional). Replace with a typed sentinel:
_UNSET_FASTAPI_APP: typing.Final = typing.cast("fastapi.FastAPI", object())
The cast suppresses the type lie; the sentinel makes __post_init__'s
identity check (is _UNSET_FASTAPI_APP) clearer than the prior
truthiness check (not self.application). FastAPIConfig stays frozen,
so the object.__setattr__(self, "application", ...) in __post_init__
remains — only the default and the check change.
No behavior change. 128/128 tests pass.
Closes REF-6 and LOW-4 from the audit.
…one guard Replace the local _UNSET_FASTAPI_APP cast with the UnsetType / UNSET pattern from modern-di, exposed via lite_bootstrap.types. FastAPIConfig.application is now honestly typed as fastapi.FastAPI | UnsetType; internal access sites narrow through a _narrow_app helper so the public union doesn't leak into instrument call chains. Adds a regression test for the explicit-None guard that brings coverage back to 100%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The application field is typed as fastapi.FastAPI | UnsetType — None is not in the type, so the runtime None check is defensive code for a type-violation that callers cannot reach honestly. Trust the type system; drop the guard and its regression test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The application field is typed as fastapi.FastAPI | UnsetType — None is not in the type, so the runtime None check is defensive code for a type-violation that callers cannot reach honestly. Trust the type system; drop the guard and its regression test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap dev dep httpx -> httpx2 to match starlette's TestClient deprecation notice, and switch the litestar OTEL span-naming test's path parameter to the FromPath[int] form the framework now recommends. Drops 2 warnings from the suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Two related cleanups in one PR:
LoggingInstrumentandOpenTelemetryInstrumentcached mutable runtime state viaobject.__setattr__workarounds because they were declaredfrozen=True. Python's dataclass rules forbid surgically droppingfrozen=Truefrom one subclass while the parent stays frozen — the cascade is required.BaseInstrumentand all 22 instrument subclasses losefrozen=True. The 4object.__setattr__call sites inLoggingInstrumentandOpenTelemetryInstrumentbecome plainself._x = ...assignments. Configs all keepfrozen=True— only instruments lose immutability.FastAPIConfig.applicationuseddefault=Nonewith a# ty: ignore[invalid-assignment]. Replaced with a typed sentinel_UNSET_FASTAPI_APP: typing.Final = typing.cast("fastapi.FastAPI", object()). The__post_init__check changes fromif not self.application:toif self.application is _UNSET_FASTAPI_APP:. Added an explicitNoneguard withConfigurationErrorto prevent silentAttributeErrorif a caller explicitly passesNone(was a tolerated type-violation pre-change; now fails cleanly).12 files modified; 24 dataclass declarations lose
frozen=True; 4object.__setattr__call sites simplified; 1# ty: ignoreremoved; 1 explicit-None guard added.No behavior change for typed callers. 128/128 tests pass.
Closes REF-6 and LOW-4 from an internal audit.
Why the cascade
Python's
@dataclasses.dataclassenforces that a non-frozen dataclass cannot inherit from a frozen one (and vice versa) —TypeErrorat class definition. To dropfrozen=TruefromLoggingInstrument(REF-6's stated target),BaseInstrumentmust also drop it, which propagates to every other instrument subclass. The sequencing spec's PR13 section called for a surgical 2-class change; the actual cascade is 24 classes.Test plan
just test— 128/128.just lint— clean (no# ty: ignoreleft in FastAPIConfig).grep -n "frozen=True" lite_bootstrap/instruments/ lite_bootstrap/bootstrappers/— only config classes match.LoggingInstrument's andOpenTelemetryInstrument's direct assignments preserve thetry/finallyexception safety from PR3.Notes for reviewer
bootstrap_configoverride invariance in framework subclasses (e.g.,FastAPICorsInstrument'sbootstrap_config: FastAPIConfigoverridingCorsInstrument'sbootstrap_config: CorsConfig). This is a Pyright-only concern about mutable-field covariance;ty checkpasses clean. Worth a separate look later if the team cares about Pyright; out of scope for this PR.Noneguard inFastAPIConfig.__post_init__is added in response to code review — it catches the new behavioral edge case whereapplication=Nonewould otherwise hitAttributeErrorinstead of building a fresh app like the pre-PR truthiness check did.🤖 Generated with Claude Code