ARCHIVED: This technical design was archived on 2025-12-22 when the reflection module was removed from the codebase. The DI module now uses Python standard library introspection (
inspect,typing.get_type_hints()) instead of the custom reflection system.
๐ Technical Design 032 โ ReflectionContext Integration & MemberBuilder Migration¶
| Field | Value |
|---|---|
| ID | TD-032 |
| Title | ReflectionContext Integration โ Wire Engine + Strategies, Remove MemberBuilder |
| Status | Draft |
| Owner | Reflection maintainers |
| Audience | Reflection/DI contributors, existing consumers of ReflectionContext |
| Created | 2025-11-15 |
| Last Updated | 2025-11-15 (Initial draft) |
| Reviewers | TBD |
| Approvers | TBD |
| Tracking | Phase C.3 of 002-reflection-modernization-roadmap.md |
| Related | TD-033 โ ReflectionEngine & HandleRegistry, TD-031 โ Handle Generation Strategy Protocol, TD-027 โ ReflectionContext API |
๐งญ 1. Summary¶
TD-033 and TD-031 built the foundation (Engine + Registry + Strategies). TD-032 wires everything into ReflectionContext and removes MemberBuilder:
- ReflectionContext integration: Update constructor to use ReflectionEngine instead of MemberBuilder
- Component migration: Update all reflection components (ImportResolver, Scanner, TypeResolver) to use engine
- API refinement: Split
clear_caches()intoclear_computation_cache()+clear_handle_registry() - MemberBuilder removal: Delete legacy builder after migration complete
- Full regression validation: All 791 existing tests pass with zero public API changes
At a glance: - Phase C.1 (TD-033): Built engine + registry โ - Phase C.2 (TD-031): Built strategies + specs โ - Phase C.3 (This TD): Wire engine into ReflectionContext, remove MemberBuilder - Zero breaking changes to public API - All existing tests pass without modification - Performance within 10% of baseline
๐ 2. Context and Problem Statement¶
2.1 Current State (After TD-033 + TD-031)¶
graph TB
subgraph "After TD-031"
Engine["ReflectionEngine<br/>(strategy-based)"]
Registry["HandleRegistry"]
Strategies["7 Core Mappers"]
end
subgraph "Existing (Unchanged)"
Context["ReflectionContext<br/>(uses MemberBuilder)"]
ImportResolver["PythonImportResolver<br/>(uses MemberBuilder)"]
Scanner["PackageScanner<br/>(uses MemberBuilder)"]
TypeResolver["RuntimeTypeResolver<br/>(uses MemberBuilder)"]
end
subgraph "Legacy"
MemberBuilder["MemberBuilder<br/>(~450 lines)"]
end
Context --> MemberBuilder
ImportResolver --> MemberBuilder
Scanner --> MemberBuilder
TypeResolver --> MemberBuilder
Engine -.->|Not yet wired| Context
Registry -.->|Not yet used| Context
classDef new fill:none,stroke:#4CAF50,stroke-width:2px,color:#4CAF50;
classDef existing fill:none,stroke:#FF9800,stroke-width:2px,color:#FF9800;
classDef legacy fill:none,stroke:#F44336,stroke-width:2px,color:#F44336,stroke-dasharray: 5 5;
class Engine,Registry,Strategies new;
class Context,ImportResolver,Scanner,TypeResolver existing;
class MemberBuilder legacy; Problem: Engine + strategies exist but aren't used. ReflectionContext still uses MemberBuilder.
2.2 Target State (After TD-032)¶
graph TB
subgraph "Integrated"
Context["ReflectionContext<br/>(uses Engine)"]
ImportResolver["PythonImportResolver<br/>(uses Engine)"]
Scanner["PackageScanner<br/>(uses Engine)"]
TypeResolver["RuntimeTypeResolver<br/>(uses Engine)"]
end
subgraph "Foundation"
Engine["ReflectionEngine<br/>(strategy-based)"]
Registry["HandleRegistry"]
Strategies["7 Core Mappers"]
end
Context --> Engine
ImportResolver --> Engine
Scanner --> Engine
TypeResolver --> Engine
Engine --> Registry
Engine --> Strategies
MemberBuilder["โ MemberBuilder<br/>(REMOVED)"]
classDef integrated fill:none,stroke:#4CAF50,stroke-width:2px,color:#4CAF50;
classDef foundation fill:none,stroke:#1976D2,stroke-width:2px,color:#1976D2;
classDef removed fill:none,stroke:#F44336,stroke-width:2px,color:#F44336,stroke-dasharray: 5 5;
class Context,ImportResolver,Scanner,TypeResolver integrated;
class Engine,Registry,Strategies foundation;
class MemberBuilder removed; 2.3 Goals¶
- Wire ReflectionEngine into ReflectionContext: Replace MemberBuilder with engine in constructor
- Update reflection components: ImportResolver, Scanner, TypeResolver delegate to engine
- Refine cache API: Split
clear_caches()into two methods (computation vs identity) - Remove MemberBuilder: Delete class after all migrations complete
- Zero public API changes: Existing code continues to work without modification
- Full regression validation: All 791 tests pass
2.4 Out of Scope¶
- Lazy loading (Phase C.4)
- Public engine exposure (future decision; engine remains internal for now)
- New ReflectionContext APIs (only refining
clear_caches())
2.5 Assumptions¶
- TD-033 complete: Engine + registry functional
- TD-031 complete: Strategies produce outputs matching MemberBuilder
- Test suite unchanged: All 791 tests must pass without modification (except clear_caches() calls)
โ๏ธ 3. Detailed Design¶
3.1 ReflectionContext Integration¶
Before (Current)¶
class ReflectionContext:
def __init__(
self,
*,
cache: HierarchicalCache | None = None,
metrics: ReflectionMetrics | None = None,
type_hint_config: TypeHintConfig | None = None,
) -> None:
self._cache = cache or ConcurrentHierarchicalCache()
self._metrics = metrics or ReflectionMetricsCollector()
# Legacy: MemberBuilder
self._member_builder = MemberBuilder(cache=self._cache)
self._type_resolver = RuntimeTypeResolver(builder=self._member_builder)
self._import_resolver = PythonImportResolver(builder=self._member_builder, metrics=metrics)
self._scanner = PackageScanner(imports=self._import_resolver, builder=self._member_builder, metrics=metrics)
After (TD-032)¶
from loryth.common.reflection.engine import ReflectionEngine
class ReflectionContext:
def __init__(
self,
*,
cache: HierarchicalCache | None = None,
metrics: ReflectionMetrics | None = None,
type_hint_config: TypeHintConfig | None = None,
) -> None:
self._cache = cache or ConcurrentHierarchicalCache()
self._metrics = metrics or ReflectionMetricsCollector()
# NEW: ReflectionEngine (strategy-based)
self._engine = ReflectionEngine(cache=self._cache)
self._type_resolver = RuntimeTypeResolver(engine=self._engine)
self._import_resolver = PythonImportResolver(engine=self._engine, metrics=metrics)
self._scanner = PackageScanner(imports=self._import_resolver, engine=self._engine, metrics=metrics)
# ... rest unchanged ...
3.2 Component Migration¶
3.2.1 RuntimeTypeResolver¶
# Before
class RuntimeTypeResolver(TypeResolver):
def __init__(self, builder: MemberBuilder) -> None:
self._builder = builder
def of_type(self, t: type[Any]) -> TypeHandle:
return self._builder.build_type(t)
# After
class RuntimeTypeResolver(TypeResolver):
def __init__(self, engine: ReflectionEngine) -> None:
self._engine = engine
def of_type(self, t: type[Any]) -> TypeHandle:
return self._engine.resolve_type(t)
def of_module(self, m: ModuleType) -> ModuleHandle:
return self._engine.resolve_module(m)
3.2.2 PythonImportResolver¶
# Before
class PythonImportResolver(ImportResolver):
def __init__(self, builder: MemberBuilder, metrics: ReflectionMetrics) -> None:
self._builder = builder
self._metrics = metrics
def resolve_type(self, dotted_path: str) -> TypeHandle:
module_name, _, type_name = dotted_path.rpartition(".")
module = importlib.import_module(module_name)
type_ = getattr(module, type_name)
return self._builder.build_type(type_)
# After
class PythonImportResolver(ImportResolver):
def __init__(self, engine: ReflectionEngine, metrics: ReflectionMetrics) -> None:
self._engine = engine
self._metrics = metrics
def resolve_type(self, dotted_path: str) -> TypeHandle:
module_name, _, type_name = dotted_path.rpartition(".")
module = importlib.import_module(module_name)
type_ = getattr(module, type_name)
return self._engine.resolve_type(type_)
3.2.3 PackageScanner¶
# Before
class PackageScanner(Scanner):
def __init__(self, imports: ImportResolver, builder: MemberBuilder, metrics: ReflectionMetrics) -> None:
self._imports = imports
self._builder = builder
self._metrics = metrics
# After
class PackageScanner(Scanner):
def __init__(self, imports: ImportResolver, engine: ReflectionEngine, metrics: ReflectionMetrics) -> None:
self._imports = imports
self._engine = engine
self._metrics = metrics
# Uses engine.find_handles_by_prefix() for graph queries
3.3 Cache API Refinement¶
Before (Confusing)¶
def clear_caches(self, namespace: tuple[str, ...] | None = None) -> None:
"""Clear caches (destroys identity AND performance cache)."""
if namespace is None:
self._cache.clear()
else:
self._cache.clear(namespace)
After (Clear Semantics)¶
def clear_computation_cache(self, namespace: tuple[str, ...] | None = None) -> None:
"""Clear performance cache (type hints, annotations).
Preserves handle identity (registry unchanged).
Use for invalidating cached computations without destroying handles.
:param namespace: Optional namespace to limit clearing scope.
:type namespace: tuple[str, ...] | None
:rtype: None
Example::
>>> # Clear cached type hints only
>>> context.clear_computation_cache(("type_hints",))
"""
self._engine.clear_cache(namespace)
def clear_handle_registry(self, prefix: str | None = None) -> None:
"""Clear handle registry (destroys identity).
Use for test isolation when you need to reset reflection state.
All handles will be rebuilt on next resolution.
:param prefix: Optional prefix to limit clearing scope.
:type prefix: str | None
:rtype: None
Example::
>>> # Test isolation: clear everything
>>> context.clear_handle_registry()
>>> # Clear specific module
>>> context.clear_handle_registry("my_app.models")
"""
self._engine.clear_registry(prefix)
def clear_caches(self, namespace: tuple[str, ...] | None = None) -> None:
"""Clear both computation cache and handle registry.
DEPRECATED: Use clear_computation_cache() or clear_handle_registry() for clarity.
This method clears BOTH for backward compatibility.
:param namespace: Optional namespace to limit clearing scope.
:type namespace: tuple[str, ...] | None
:rtype: None
"""
# Clear both (backward compatibility)
self.clear_computation_cache(namespace)
if namespace is None:
self.clear_handle_registry()
else:
# Convert tuple namespace to prefix string
prefix = ".".join(str(n) for n in namespace)
self.clear_handle_registry(prefix)
๐งช 4. Test Strategy¶
4.1 Test Matrix¶
| Test Category | Coverage Target | Key Validation |
|---|---|---|
| Integration Tests | 100% | ReflectionContext โ Engine โ Strategies workflow |
| Component Migration | 100% | ImportResolver, Scanner, TypeResolver use engine |
| Regression Validation | 100% | All 791 existing tests pass without modification |
| Cache API | 100% | clear_computation_cache() vs clear_handle_registry() semantics |
| Performance | Baseline ยฑ10% | Engine-based resolution within 10% of MemberBuilder |
4.2 Migration Validation Checklist¶
- โ ReflectionContext constructor updated to create Engine
- โ RuntimeTypeResolver delegates to engine.resolve_type()
- โ PythonImportResolver delegates to engine.resolve_type()
- โ PackageScanner uses engine for module resolution
- โ Cache API split into clear_computation_cache() + clear_handle_registry()
- โ All 791 tests pass without modification
- โ MemberBuilder removed from codebase
- โ Performance validated (ยฑ10% of baseline)
โ 5. Acceptance Criteria¶
Phase C.3a: ReflectionContext Integration¶
- โ
ReflectionContext.__init__()createsReflectionEngineinstead ofMemberBuilder - โ
clear_computation_cache()added - โ
clear_handle_registry()added - โ
clear_caches()marked deprecated, clears both for compatibility - โ Unit tests: cache clearing semantics validated
Phase C.3b: Component Migration¶
- โ
RuntimeTypeResolverupdated to use engine - โ
PythonImportResolverupdated to use engine - โ
PackageScannerupdated to use engine - โ Unit tests: each component tested with engine
Phase C.3c: Regression Validation¶
- โ All 791 existing tests pass without modification
- โ Integration tests: full workflows (resolve, scan, invoke, construct)
- โ Golden fixtures: outputs match MemberBuilder baseline
- โ Edge cases: circular imports, forward refs, deep nesting
Phase C.3d: MemberBuilder Removal¶
- โ
MemberBuilderclass deleted frommember_builder.py - โ
All imports of
MemberBuilderremoved - โ Cache-based identity lookups replaced with registry lookups
- โ All 791 tests still pass after removal
- โ Performance benchmarks: ยฑ10% of MemberBuilder baseline
๐ 6. Implementation Phases¶
Phase C.3a: ReflectionContext Integration (โณ Pending)¶
Tasks: 1. Update ReflectionContext.__init__(): create ReflectionEngine 2. Add clear_computation_cache() method 3. Add clear_handle_registry() method 4. Update clear_caches() to call both (backward compatibility) 5. Write unit tests: cache clearing semantics
Acceptance: ReflectionContext uses engine; cache API refined
Phase C.3b: Component Migration (โณ Pending)¶
Tasks: 1. Update RuntimeTypeResolver: replace builder with engine 2. Update PythonImportResolver: replace builder with engine 3. Update PackageScanner: replace builder with engine 4. Write unit tests: each component with engine
Acceptance: All components delegate to engine
Phase C.3c: Regression Validation (โณ Pending)¶
Tasks: 1. Run full test suite (791 tests) 2. Fix any test failures (should be minimal or zero) 3. Write integration tests: full workflows 4. Validate golden fixtures: outputs match MemberBuilder 5. Performance benchmarks: compare before/after
Acceptance: All 791 tests pass; performance acceptable
Phase C.3d: MemberBuilder Removal (โณ Pending)¶
Tasks: 1. Delete reflection/member_builder.py 2. Remove all imports of MemberBuilder 3. Remove cache-based identity lookups (replaced by registry) 4. Run full test suite again (ensure no regressions) 5. Final performance validation
Acceptance: MemberBuilder removed; all tests pass; performance validated
๐ 7. Dependencies¶
Requires: - TD-033 complete: Engine + registry functional - TD-031 complete: Strategies produce correct outputs
Blocks: - Phase C.4 (Lazy Loading): Can't add lazy loading until migration complete
๐ 8. Changelog¶
- 2025-11-15 โ Initial draft created
- ReflectionContext integration plan
- Component migration strategy
- Cache API refinement design
- MemberBuilder removal plan
- Status: Draft (awaiting TD-033 + TD-031 completion)
End of TD-032