fix(admin): union/TypeVar-safe bean introspection — admin bean-graph 500 (v26.06.99)#129
Merged
Merged
Conversation
…500 (v26.06.99)
The admin dashboard's #bean-graph view (and /beans, /beans/{name}) 500'd whenever
a bean's registration key was not a plain class. The idiomatic
`@bean def make_foo(self) -> Foo | None` is the common trigger: typing.get_type_hints
preserves `Foo | None` as a types.UnionType, which the configuration processor
registered directly as a _registrations key. UnionType/TypeVar have no
__name__/__qualname__/__module__, so BeansProvider — deriving names/types straight
from the key — raised AttributeError. This only surfaced after the v26.06.95 startup
fix made union beans bootable.
Fixed at three layers:
- Source: ApplicationContext no longer registers a non-class return hint as a
_registrations key (the concrete type(result) registration already backs
single-bean resolution, so nothing is lost).
- Provider: BeansProvider derives names/types via union/TypeVar-safe _key_name /
_key_qualname helpers; condition values are coerced JSON-safe; a raising on_class
check() is reported not-passed; a raising descriptor in the autowired-field scan
is skipped.
- Backstop: admin API handlers convert any unexpected error into a logged, structured
JSON 500 instead of a raw Starlette 500.
Also realigns pyfly.__version__ (had drifted to 26.06.94) so framework_version, the
CLI banner, SBOMs, and admin asset cache-busting report the shipped version.
Regression tests drive the REAL ApplicationContext/Container (not the mocked
container the prior admin tests used, which is why these 500s shipped):
tests/context/test_bean_method_union_return.py,
tests/admin/test_beans_provider_realcontext.py.
Bumps 26.6.98 -> 26.6.99; updates CHANGELOG + README badge.
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
Fixes the admin dashboard
#bean-graph500 (and/beans,/beans/{name}) that occurs whenever a bean's registration key is not a plain class.Root cause: the idiomatic
@bean def make_foo(self) -> Foo | None.typing.get_type_hintspreservesFoo | Noneas atypes.UnionType, andApplicationContext._process_configurationsregistered that union object directly as a_registrationskey. Atypes.UnionType(andTypeVar) has no__name__/__qualname__/__module__, soBeansProvider— which derived bean names/types straight from the key — raisedAttributeError: 'types.UnionType' object has no attribute '__name__'.Fix (defense in depth, 3 layers)
ApplicationContextno longer registers a non-class return hint as a_registrationskey. The concretetype(result)registration already backs single-bean resolution, so nothing is lost.BeansProviderderives names/types via union/TypeVar-safe_key_name/_key_qualnamehelpers; condition values coerced JSON-safe; a raisingon_classcheck()is reported not-passed; a raising descriptor in the autowired scan is skipped.Also covers three additional reproduced
/beans/{name}detail 500s (generic-alias conditions, raisingon_classcheck, raising descriptor), and realigns the stalepyfly.__version__(26.06.94→26.06.99) that fed the dashboardframework_version, CLI banner, SBOMs, and admin asset cache-busting.Why it slipped through
The prior admin tests (
test_beans_provider_enhanced.py) use a mocked container whose keys are always vanilla classes — real type-hint resolution was never exercised. New regression tests drive the realApplicationContext/Container:tests/context/test_bean_method_union_return.py(the exact@bean -> Foo | Nonescenario, end to end through the admin provider)tests/admin/test_beans_provider_realcontext.py(union/TypeVarkeys, generic-alias conditions, raising check, raising descriptor)Verification
ruff check✓ ·ruff format --check✓ ·mypy src/pyfly --strict✓ (changed files)tests/admin tests/context tests/container tests/cli tests/observability→ 807 passedmain, pass here.🤖 Generated with Claude Code