Skip to content

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 030 โ€” Reflection Handle Hierarchy Rationalization

Field Value
ID TD-030
Title Reflection Handle Hierarchy Rationalization
Status โœ… Complete (Phase 1 โœ…, Phase 2 โœ…, Phase 3 โœ…, Phase 4 โœ…, Phase 5 โœ…)
Owner Reflection maintainers
Audience Reflection/DI contributors, type system maintainers
Created 2025-10-25
Last Updated 2025-10-26 (Phase 5 Complete)
Reviewers TBD
Approvers TBD
Tracking Phase I of 002-reflection-modernization-roadmap.md
Related TD-033 โ€” ReflectionEngine & HandleRegistry (depends on this), TD-031 โ€” Handle Generation Strategy Protocol (depends on this), TD-027 โ€” API Simplification, TD-028 โ€” Metrics Architecture

๐Ÿงญ 1. Summary

The current reflection system uses passive "Info" classes that obscure their purpose as active references and navigation tools. This design establishes a clearer five-phase refactoring that renames all *Info classes to *Handle classes and reorganizes the codebase for better maintainability. Phase 1 (complete) established a clean type hierarchy (ReflectionInfo โ†’ ModuleInfo/MemberInfo) with explicit ownership semantics. Phase 2 (planned) completes the architectural vision by renaming to Handles and reorganizing into individual files within the handles package. Subsequent phases add ownership properties, consolidate APIs, and enable Phase C (Member Parsing Seam) implementation.

At a glance:

  • Phase 1 โœ… Complete: ReflectionInfo/MemberInfo hierarchy with explicit module/type properties (791 tests passing)
  • Phase 2 โœ… Complete: Rename *Info โ†’ *Handle; reorganize into handles/ package (one class per file)
  • Phase 3 โœ… Complete: Ownership properties added (module, type) to all member types
  • Phase 4 โœ… Complete: Unified modules model (PackageHandle removed, merged into ModuleHandle with is_package property)
  • Phase 5 โœ… Complete: Scanner integration unified (scan_package merged into scan_module with recursive parameter)
  • Handles terminology: Better semantics for "active references" vs passive data containers
  • One class per file: Improved discoverability, maintainability, and independent evolution
  • Unified modules: Consolidate PackageInfo into ModuleInfo with is_package property
  • Unified scanning: Single scan_module() API with recursive parameter replaces separate scan_package()
  • Clear ownership: Explicit module and type properties enable straightforward navigation
  • Foundation for Phase C: Handles semantics align with Member Parsing Seam design
  • Zero consumer impact: Internal refactor; public APIs and DI code unchanged

๐Ÿ“š 2. Context and Problem Statement

The current type hierarchy has MemberInfo as the root abstraction for all introspectable entities: modules, packages, types, methods, fields, and parameters. Ownership semantics are implicit and scattered across code:

  • TypeInfo has module property (it belongs to a module)
  • MethodInfo/FieldInfo/PropertyInfo have no explicit type property (relationship to class is implicit)
  • ParameterInfo has no clear parent linkage

This design creates navigation challenges and makes the conceptual model harder to reason about.

Pain points:

  1. Implicit ownership: No direct way to navigate from method โ†’ type โ†’ module ownership chain
  2. Redundant classes: PackageInfo duplicates ModuleInfo; Python treats packages as modules with __path__.
  3. Unclear scope: Hard to answer "what scope contains this member?" without special cases
  4. Complicates Phase C: The Member Parsing Seam design needs explicit "member belongs to X" semantics for clean strategy composition.

Prior work: TD-027 (API Simplification) narrowed the public surface of ReflectionContext; TD-028 (Metrics Architecture) introduced strongly-typed metrics. This TD completes the foundational cleanup.

2.1 Goals

  1. Separate containers from members conceptually and in code
  2. Consolidate PackageInfo into ModuleInfo (eliminate redundancy)
  3. Provide a clear type hierarchy that reflects Python's object model
  4. Foundation for Phase C: Enable parsing strategies to clearly distinguish discovery (members) from orchestration (containers)
  5. Maintain zero breaking changes for DI consumers

2.2 Out of Scope

  • Parsing strategy design (Phase C)
  • Type checking redesign (Phase F)
  • Annotation and type-hint provider consolidation (Phase E)
  • Performance optimization
  • Scanning algorithm unification (Phase D)

2.3 Assumptions and Constraints

  • Python 3.13+ with strict mypy and PEP 695 generics
  • Alpha stage: Breaking changes allowed with migration guidance (no deprecation period)
  • Must complete before Phase C implementation (though alphabetically later)
  • Zero breaking changes for DI consumers โ€” this is internal refactoring
  • Existing test coverage maintained โ€” all tests pass with new hierarchy

2.4 Terminology: Handle vs Info

Decision: Rename all *Info classes to *Handle to better convey their purpose as lightweight references to introspectable entities, not passive data containers.

Rationale:

The term "handle" better communicates that these are: - Active references to runtime entities (modules, types, methods) - Navigation tools for traversing the reflection graph - Lazy accessors that may trigger introspection on demand - Lightweight proxies that don't duplicate the underlying entity

This aligns with established patterns in other reflection systems (Java's MethodHandle, C#'s unsafe handles).

Alternatives considered:

Term Pros Cons Status
Handle Clear intent (reference/accessor), established pattern (Java), concise None significant โœ… Selected
Info Familiar (current), simple Suggests passive data; doesn't convey navigation/reference semantics โŒ Rejected
Ref Very concise, common in systems programming Ambiguous (reference vs referent); conflicts with Python's weakref โŒ Rejected
Proxy Clearly indicates delegation pattern Heavier semantics; suggests interception/wrapping over introspection โŒ Rejected
Descriptor Precise reflection terminology Conflicts with Python's descriptor protocol; too specific โŒ Rejected
Meta Concise, conveys metadata Overloaded term (metaclass); unclear in context โŒ Rejected

Naming convention: All classes follow *Handle pattern: - ReflectionInfo โ†’ ReflectionHandle (base) - MemberInfo โ†’ MemberHandle (owned entities) - ModuleInfo โ†’ ModuleHandle (scopes) - TypeInfo โ†’ TypeHandle (class introspection) - FunctionInfo โ†’ FunctionHandle (function introspection) - MethodInfo โ†’ MethodHandle (method introspection) - And so on for all member types

๐Ÿงฉ 3. Design Overview

The design introduces a simplified ReflectionInfo abstract base that branches into two concrete categories:

  1. ModuleInfo โ€” Scopes that contain members (modules and packages unified)
  2. MemberInfo โ€” Entities that belong to a scope (types, functions, methods, fields, parameters)

Ownership is explicit through properties: - All MemberInfo subclasses have a module: ModuleInfo property - Class-level MemberInfo subclasses (MethodInfo, FieldInfo, PropertyInfo) also have a type: TypeInfo property - TypeInfo occupies a special dual role: it is a member of a module (extends MemberInfo) but also a container for its own members (methods, fields, properties)

This explicit ownership model eliminates ambiguity and enables clear navigation of the member hierarchy.

3.1 Key Components

Component Purpose
ReflectionInfo Abstract base for all introspectable entities; common properties (qualname, name, doc, annotations)
ModuleInfo Concrete scope for Python modules and packages (unified); includes is_package property and submodules iterator
MemberInfo Abstract base for owned members (types, functions, methods, fields, parameters); defines module property
TypeInfo Dual role: member of module (has module property) + container for class members (has methods, fields iterators)

3.2 Type Hierarchy Overview

graph TD
    RI["ReflectionInfo<br/>(abstract)"]
    MOD["ModuleInfo<br/>(scope)"]
    MI["MemberInfo<br/>(owned)"]

    RI --> MOD
    RI --> MI

    MOD -. contains .-> MI

    classDef base fill:none,stroke:#8E24AA,stroke-width:2px,color:#8E24AA
    classDef scope fill:none,stroke:#00C853,stroke-width:2px,color:#00C853
    classDef member fill:none,stroke:#1976D2,stroke-width:2px,color:#1976D2

    class RI base
    class MOD scope
    class MI member

3.2a Complete MemberInfo Hierarchy

graph TD
    RI["ReflectionInfo"]
    MOD["ModuleInfo<br/>is_package: bool<br/>classes: Iterator<br/>functions: Iterator<br/>submodules: Iterator"]
    MI["MemberInfo<br/>module: ModuleInfo"]

    TI["TypeInfo<br/>module: ModuleInfo<br/>methods: Iterator<br/>fields: Iterator"]
    FI["FunctionInfo<br/>module: ModuleInfo"]

    MTH["MethodInfo<br/>module: ModuleInfo<br/>type: TypeInfo"]
    FLD["FieldInfo<br/>module: ModuleInfo<br/>type: TypeInfo"]
    PRP["PropertyInfo<br/>module: ModuleInfo<br/>type: TypeInfo"]

    PARAM["ParameterInfo<br/>module: ModuleInfo<br/>type: TypeInfo"]

    RI --> MOD
    RI --> MI

    MI --> TI
    MI --> FI
    MI --> MTH
    MI --> FLD
    MI --> PRP
    MI --> PARAM

    MOD -. "contains" .-> TI
    MOD -. "contains" .-> FI
    TI -. "contains" .-> MTH
    TI -. "contains" .-> FLD
    TI -. "contains" .-> PRP
    MTH -. "contains" .-> PARAM

    classDef base fill:none,stroke:#8E24AA,stroke-width:2px,color:#8E24AA
    classDef scope fill:none,stroke:#00C853,stroke-width:2px,color:#00C853
    classDef member fill:none,stroke:#1976D2,stroke-width:2px,color:#1976D2
    classDef dual fill:none,stroke:#FFB300,stroke-width:2px,color:#FFB300

    class RI base
    class MOD scope
    class MI member
    class TI dual

3.3 Interface Impact (Before โ†’ After)

Area Before After
Type hierarchy MemberInfo (root) for all entities ReflectionInfo (root) โ†’ ModuleInfo / MemberInfo
Member ownership Implicit (scattered across code) Explicit: module and type properties on MemberInfo
Package representation PackageInfo (separate class) ModuleInfo.is_package property
MemberBuilder build_package(module) โ†’ PackageInfo build_module(module) โ†’ ModuleInfo (consolidated)
ReflectionContext resolve_package(path) โ†’ PackageInfo resolve_module(path) โ†’ ModuleInfo (consolidated)
PackageScanner scan_package(pkg, recursive=True) scan_module(mod, recursive=True)
Enums MemberKind.MODULE, MemberKind.PACKAGE Removed; optionally add ContainerKind.MODULE
Cache keys ("builder", "package", qualname) ("module", qualname) for modules
Terminology *Info classes (MemberInfo, TypeInfo, etc.) *Handle classes (MemberHandle, TypeHandle, etc.)
Package location member_types/ (base.py, structural.py, callables.py, containers.py) handles/ (one file per class)
Enum location reflection/enums.py handles/enums.py
Import path from .member_info import TypeInfo from .handles import TypeHandle

3.4 Package Restructuring: handles

New package structure: loryth.common.reflection.handles

Organization: - One class per file: Each handle class gets its own module for clarity and maintainability - Enums included: Move reflection enums into handles package - Flat namespace: Direct imports from handles package for ergonomics

File structure:

src/loryth/common/reflection/handles/
โ”œโ”€โ”€ __init__.py           # Re-exports all handles and enums
โ”œโ”€โ”€ reflection_handle.py  # ReflectionHandle ABC
โ”œโ”€โ”€ member_handle.py      # MemberHandle ABC
โ”œโ”€โ”€ module_handle.py      # ModuleHandle (scope)
โ”œโ”€โ”€ package_handle.py     # PackageHandle (to be removed in Phase 4)
โ”œโ”€โ”€ type_handle.py        # TypeHandle (dual role: member + container)
โ”œโ”€โ”€ callable_handle.py    # CallableHandle ABC
โ”œโ”€โ”€ function_handle.py    # FunctionHandle
โ”œโ”€โ”€ method_handle.py      # MethodHandle
โ”œโ”€โ”€ constructor_handle.py # ConstructorHandle
โ”œโ”€โ”€ field_handle.py       # FieldHandle
โ”œโ”€โ”€ property_handle.py    # PropertyHandle
โ”œโ”€โ”€ parameter_handle.py   # ParameterHandle
โ””โ”€โ”€ enums.py             # Visibility, MemberKind, MethodKind, Variance

Import patterns:

# Before
from loryth.common.reflection.member_info import TypeInfo, MemberInfo
from loryth.common.reflection.enums import MemberKind

# After
from loryth.common.reflection.handles import TypeHandle, MemberHandle, MemberKind

Rationale for one-class-per-file approach: - Clarity: Each class is independently discoverable and testable - Maintainability: Smaller focused modules reduce cognitive load - Discoverability: File names match class names exactly (type_handle.py for TypeHandle) - Growth: Easier to extend individual classes without touching unrelated code - Dependencies: Clear module boundaries make import dependencies explicit

3.5 Removal/Deprecation Policy

Alpha stage: Clean break.

  • PackageInfo class removed entirely (not available)
  • build_package(), resolve_package(), scan_package() methods removed
  • MemberKind.MODULE and MemberKind.PACKAGE removed from enums
  • All changes synchronized in single update (all tests, all code updated together)
  • No deprecation period; no aliases
  • Tests and migration examples provided in documentation

๐Ÿ“ 4. Implementation Details

4.1 Type Hierarchy & MemberInfo Abstraction

ReflectionInfo (abstract base):

from abc import ABC, abstractmethod
from collections.abc import Iterator

class ReflectionInfo(ABC):
    """Abstract base for all introspectable entities.

    Common properties for modules and members.
    """

    @property
    @abstractmethod
    def qualname(self) -> str:
        """Fully qualified name (e.g., 'loryth.common.reflection.MethodInfo')."""
        ...

    @property
    def name(self) -> str:
        """Short name (last component of qualname)."""
        return self.qualname.split(".")[-1]

    @property
    @abstractmethod
    def doc(self) -> str | None:
        """Docstring if available."""
        ...

    @property
    @abstractmethod
    def annotations(self) -> Iterator['AnnotationInfo']:
        """Annotations attached to this entity."""
        ...

MemberInfo (abstract, with explicit ownership):

class MemberInfo(ReflectionInfo, ABC):
    """Abstract base for owned members.

    All members belong to a ModuleInfo. Class-level members additionally
    belong to a TypeInfo.
    """

    @property
    @abstractmethod
    def module(self) -> 'ModuleInfo':
        """Module that owns this member."""
        ...

    # For class-level members only (MethodInfo, FieldInfo, PropertyInfo)
    # Subclasses will override to provide TypeInfo

See source code for concrete implementations (ModuleInfo, TypeInfo, FunctionInfo, MethodInfo, etc.).

4.2 ModuleInfo: Unified Container

ModuleInfo (scope for modules and packages):

class ModuleInfo(ReflectionInfo):
    """Scope for Python modules and packages (unified).

    Packages (modules with __path__) and regular modules both use
    ModuleInfo; distinguish via is_package property.
    """

    @property
    def is_package(self) -> bool:
        """True if this module is a package (has __path__)."""
        ...

    @property
    def classes(self) -> Iterator['TypeInfo']:
        """Types defined directly in this module."""
        ...

    @property
    def functions(self) -> Iterator['FunctionInfo']:
        """Functions defined directly in this module."""
        ...

    @property
    def submodules(self) -> Iterator['ModuleInfo']:
        """Submodules (empty if not a package)."""
        ...

4.3 API Changes in MemberBuilder, ReflectionContext, and PackageScanner

MemberBuilder updates:

  • Remove build_package(module: ModuleType) -> PackageInfo method
  • Update build_module() to transparently handle packages (hasattr(module, '__path__'))
  • Cache key changes: ("module", qualname) for all modules
  • build_type(), build_function() signatures and behavior unchanged

ReflectionContext updates:

  • Remove resolve_package(path: str) -> PackageInfo method
  • resolve_module(path: str) -> ModuleInfo handles both modules and packages
  • scan_module(module, predicate, recursive: bool) replaces scan_package()

PackageScanner updates:

  • Remove scan_package() method entirely
  • scan_module(module, predicate, recursive) single entry point
  • Internally: check module.is_package and recursive for recursion logic

4.4 Member Container Relationships

TypeInfo (dual role):

TypeInfo extends MemberInfo with module property (it's a member of a module) AND acts as container for class members:

class TypeInfo(MemberInfo):
    """Type (class) member with dual role.

    1. As member: has module property (belongs to ModuleInfo)
    2. As container: has methods/fields/properties iterators
    """

    @property
    def module(self) -> ModuleInfo:
        """Module containing this type."""
        ...

    @property
    def methods(self) -> Iterator['MethodInfo']:
        """Methods defined in this type."""
        ...

    @property
    def fields(self) -> Iterator['FieldInfo']:
        """Fields defined in this type."""
        ...

FunctionInfo (module-level function):

Functions belong only to a module (not a class):

class FunctionInfo(MemberInfo):
    """Function member."""

    @property
    def module(self) -> ModuleInfo:
        """Module containing this function."""
        ...

Class members (MethodInfo, FieldInfo, PropertyInfo):

Class members belong to both a module (transitively) and a type:

class MethodInfo(MemberInfo):
    """Method member."""

    @property
    def module(self) -> ModuleInfo:
        """Module containing the class that owns this method."""
        ...

    @property
    def type(self) -> TypeInfo:
        """Type (class) that owns this method."""
        ...

Same pattern for FieldInfo, PropertyInfo.

4.5 Backward Compatibility & Breaking Changes

Removed entirely: - PackageInfo class (no compatibility shim) - build_package(), resolve_package(), scan_package() methods - MemberKind.MODULE and MemberKind.PACKAGE enum values

Changed behavior: - build_module() now returns ModuleInfo for packages (was PackageInfo) - resolve_module() now returns ModuleInfo for packages (was PackageInfo) - Cache keys simplified: ("module", qualname) instead of ("builder", "module/package", ...)

Preserved behavior: - DI bootstrap unchanged (uses ReflectionContext, unaware of internals) - Type resolution semantics identical - Member discovery semantics identical - Public API method names and signatures mostly unchanged (only removals)

โš™๏ธ 5. Semantics

Ownership model:

  • Modules (ModuleInfo): Top-level scopes. Do not belong to anything.
  • Members (MemberInfo): All have module: ModuleInfo property. Everything introspectable except modules is a member.
  • Class members: MethodInfo, FieldInfo, PropertyInfo additionally have type: TypeInfo property.
  • Parameters: ParameterInfo also have module and type (if parameter of a method) properties.

TypeInfo dual role:

  • As member of module: type_info.module returns the containing ModuleInfo
  • As container for class members: type_info.methods, type_info.fields provide access to class members
  • Reflects Python semantics: classes are module members that also contain members

Navigation semantics:

module โ†’ type โ†’ method โ†’ parameter
  โ†“        โ†“       โ†“
 (owns)  (owns)  (owns)

Every entity can navigate "upward" to its owning container: - method.type โ†’ containing class - method.module โ†’ containing module - type.module โ†’ containing module - function.module โ†’ containing module

Package handling:

  • Packages are modules with __path__ attribute (Python's native model)
  • ModuleInfo.is_package boolean property distinguishes packages from regular modules
  • ModuleInfo.submodules iterator is empty for non-packages
  • Caller controls recursion via scan_module(..., recursive=bool) parameter

๐Ÿงช 6. Examples

6.1 Basic Usage

from loryth.common.reflection import ReflectionContext

context = ReflectionContext()

# Resolve module (works for packages too)
module = context.resolve_module("loryth.common.reflection")
print(module.is_package)  # True if package, False otherwise

# Access and navigate members
for cls in module.classes:
    print(f"Type: {cls.qualname}")
    print(f"Module: {cls.module.qualname}")  # Navigate back to owning module

6.2 Advanced Patterns

# Scan module recursively, access ownership chain
from loryth.common.reflection import TypeInfo, MethodInfo

for member in context.scan_module("loryth.common", recursive=True):
    if isinstance(member, TypeInfo):
        # Navigate: type โ†’ module
        print(f"{member.qualname} in module {member.module.qualname}")
        for method in member.methods:
            # Navigate: method โ†’ type โ†’ module
            print(f"  Method: {method.name} in {method.type.qualname}")

6.3 Integration Example

# MemberBuilder (internal usage)
from loryth.common.reflection.member_builder import MemberBuilder
from loryth.common.collections.concurrent import ConcurrentHierarchicalCache

builder = MemberBuilder(cache=ConcurrentHierarchicalCache())

import loryth.common.reflection as ref_module
module_info = builder.build_module(ref_module)

# Navigate package structure
if module_info.is_package:
    for submodule in module_info.submodules:
        for type_info in submodule.classes:
            for method in type_info.methods:
                # Full ownership chain: module โ†’ type โ†’ method
                print(f"{method.module.qualname}.{method.type.name}.{method.name}")

๐Ÿงช 7. Test Matrix

Unit coverage (80%+): ReflectionInfo common properties; MemberInfo.module property and navigation; ModuleInfo.is_package property and submodules iterator; TypeInfo dual role (module property + members/fields iterators); MethodInfo/FieldInfo with both module and type properties; enum changes (removal of MemberKind.MODULE/PACKAGE).

Integration coverage (comprehensive): MemberBuilder.build_module() for both modules and packages (is_package = True/False); ReflectionContext.resolve_module() for package paths; PackageScanner.scan_module(recursive=True) with submodule iteration and ownership verification; ownership chain navigation (module โ†’ type โ†’ method); cache key correctness (simplified ("module", qualname)); DI bootstrap unchanged.

Regression coverage (all existing workflows): All existing module/package scanning behavior preserved; type resolution unchanged; metrics emission behavior unchanged; DI container initialization and autowiring unaffected; member access and ownership semantics identical.

Testing notes: Unit tests focus on ownership properties (module, type); integration tests verify full ownership chains via actual modules; regression tests ensure consumer APIs (especially DI) see zero behavior changes.

โœ… 8. Acceptance Criteria

  • โณ ReflectionInfo abstract base defined with common properties (qualname, name, doc, annotations)
  • โณ MemberInfo redefined to extend ReflectionInfo with module: ModuleInfo property
  • โณ ModuleInfo extends ReflectionInfo with is_package property and submodules iterator
  • โณ PackageInfo class fully removed from codebase
  • โณ build_package() method removed from MemberBuilder
  • โณ resolve_package() method removed from ReflectionContext
  • โณ scan_package() method removed from PackageScanner
  • โณ MemberKind.MODULE and MemberKind.PACKAGE enum values removed
  • โณ All member types (MethodInfo, FieldInfo, PropertyInfo, ParameterInfo) have explicit module: ModuleInfo property
  • โณ Class-level members (MethodInfo, FieldInfo, PropertyInfo) have explicit type: TypeInfo property
  • โณ Cache keys simplified to ("module", qualname) for module resolution
  • โณ All existing tests pass with new hierarchy (no test failures)
  • โณ Integration tests verify ownership chains: module โ†’ type โ†’ method navigation
  • โณ Integration tests verify scan_module(recursive=True) with is_package checks
  • โณ Complexity per component โ‰ค10 cyclomatic, โ‰ค50 lines method length
  • โณ Zero breaking changes to DI consumer APIs (DI bootstrap unchanged)
  • โณ Architecture documentation updated with new type hierarchy and diagrams
  • โณ Phase C (TD-029) unblocked and ready for implementation

๐Ÿš€ 9. Rollout and Migration

9.1 Implementation Phases

Phase 1 โ€” Core Hierarchy Simplification: โœ… Completed

  • Create ReflectionInfo abstract base with common properties (qualname, name, doc, annotations)
  • Redefine MemberInfo to extend ReflectionInfo with module: ModuleInfo property
  • Ensure all MemberInfo subclasses preserve existing signatures and behavior
  • Update module-level imports to reflect new hierarchy (no API surface changes)
  • Outcome: Simplified two-level hierarchy in place (ReflectionInfo โ†’ ModuleInfo / MemberInfo)

Phase 2 โ€” Terminology Change & Package Restructuring:

  • Create new handles/ package directory under loryth/common/reflection/
  • Create individual files for each handle class (reflection_handle.py, member_handle.py, module_handle.py, type_handle.py, etc.)
  • Move enums.py into handles/ package; consolidate all reflection enums there
  • Rename all *Info classes to *Handle (find/replace: ReflectionInfo โ†’ ReflectionHandle, MemberInfo โ†’ MemberHandle, etc.)
  • Update all internal imports to use new handles package structure
  • Remove old member_types/ package and member_info.py re-export layer
  • Update all test code to use new handle class names and package paths
  • Run full test suite; verify all 791+ tests passing with new terminology
  • Outcome: Clean "handle" semantics with one class per file; improved code organization

Phase 3 โ€” ModuleHandle Ownership Properties:

  • Add is_package: bool property to ModuleHandle (True if module has __path__)
  • Add module: ModuleHandle property to all MemberHandle subclasses
  • Add type: TypeHandle property to class-level members (MethodHandle, FieldHandle, PropertyHandle)
  • Update member builders to populate ownership properties during construction
  • Outcome: Explicit ownership model in place, all properties accessible

Phase 4 โ€” PackageHandle Removal & API Consolidation:

  • Remove PackageHandle class entirely (search for all references)
  • Remove build_package() from MemberBuilder; build_module() handles packages transparently
  • Remove resolve_package() from ReflectionContext; resolve_module() handles packages transparently
  • Update MemberKind enum (remove MODULE, PACKAGE values if present)
  • Update cache key generation to use ("module", qualname) uniformly
  • Outcome: Single ModuleHandle API, all package/module handling unified

Phase 5 โ€” Scanner Integration & Full Test Coverage:

  • Remove scan_package() from PackageScanner; scan_module(..., recursive=True) serves both cases
  • Update scan_module() to check is_package and recursive for recursion logic
  • Update all test code: replace PackageHandle โ†’ ModuleHandle, build_package() โ†’ build_module(), scan_package() โ†’ scan_module(..., recursive=True)
  • Verify all ownership chains work (module โ†’ type โ†’ method, function โ†’ module)
  • Run full test suite; verify zero breaking changes to DI APIs
  • Outcome: Integration complete, all tests passing, Phase C unblocked

9.2 Migration Strategy

For internal codebase (synchronized across all 5 phases):

Phase 2 terminology change: - Search/replace from loryth.common.reflection.member_info import โ†’ from loryth.common.reflection.handles import - Search/replace ReflectionInfo โ†’ ReflectionHandle globally - Search/replace MemberInfo โ†’ MemberHandle globally - Search/replace ModuleInfo โ†’ ModuleHandle globally - Search/replace TypeInfo โ†’ TypeHandle globally - Search/replace *Info โ†’ *Handle for all info classes globally

Phases 4-5 package consolidation: - Search/replace from loryth.common.reflection import PackageHandle โ†’ Remove import (no longer exported) - Search/replace PackageHandle( โ†’ Becomes ModuleHandle( (consolidation) - Search/replace build_package( โ†’ becomes build_module( (MemberBuilder) - Search/replace resolve_package( โ†’ becomes resolve_module( (ReflectionContext) - Search/replace scan_package( โ†’ becomes scan_module(..., recursive=True) (PackageScanner) - Update type annotations: ModuleHandle | PackageHandle โ†’ just ModuleHandle

For DI consumers:

No migration needed. Internal refactor with zero public API changes: - ReflectionContext method signatures unchanged (except removals) - DI bootstrap behavior unchanged - Autowiring behavior unchanged - Type introspection behavior unchanged

โš ๏ธ 10. Risks and Mitigations

Risk Mitigation
Phase C blocked on Phase I completion Phase I complete (Oct 26); Phase II (terminology) unblocks Phase III (ownership); Phase V unblocks Phase C
Test coverage gaps during refactor Maintain regression test suite; run full pytest (791+ tests) after each phase completion
Incomplete *Handle migration across codebase Systematic search for "*Info" class references; automated test coverage ensures missed renames surface quickly
TypeHandle dual role creates confusion Clear docstrings explaining member-of-module vs container-for-members aspects; include examples in architecture docs
Package reorganization impacts dependencies Centralized handles/ package; clear init.py exports; no consumer-facing imports change
Migration complexity across 5 phases Each phase has clear, focused outcome; systematic phase progression minimizes coordination overhead

๐Ÿ“ˆ 11. Progress

Legend: โณ Pending ยท ๐Ÿšง In Progress ยท โœ… Completed

  • Phase 1 โ€” Core Hierarchy Simplification
  • โœ… ReflectionInfo abstract base created with common properties
  • โœ… MemberInfo redefined to extend ReflectionInfo (not be root)
  • โœ… All member types updated to extend MemberInfo
  • โœ… Module-level imports updated (backward-compatible)
  • โœ… 791 tests passing with new hierarchy
  • Phase 2 โ€” Terminology Change & Package Restructuring
  • โณ handles/ package created with individual files per class
  • โณ All *Info classes renamed to *Handle
  • โณ Enums moved into handles/ package
  • โณ All imports updated across codebase
  • โณ member_types/ and member_info.py removed
  • โณ 791+ tests passing with new terminology
  • Phase 3 โ€” ModuleHandle Ownership Properties
  • โœ… ModuleHandle.is_package property added
  • โœ… MemberHandle.module property already present (from Phase 1-2)
  • โœ… MethodHandle/FieldHandle/PropertyHandle.type property added
  • โœ… ConstructorHandle.type property alias added
  • โœ… Member builders updated to populate ownership properties
  • โœ… 791 tests passing with ownership properties
  • Phase 4 โ€” PackageHandle Removal & API Consolidation
  • โœ… PackageHandle class removed completely (deleted package_handle.py)
  • โœ… build_package() removed from MemberBuilder, resolve_package() returns ModuleHandle
  • โœ… MemberKind.MODULE and MemberKind.PACKAGE removed from enum
  • โœ… Cache keys updated to ("module", qualname) for unified module/package handling
  • โœ… PackageScanner refactored to discover submodules dynamically
  • โœ… ReflectionContext.scan() checks is_package instead of isinstance(PackageHandle)
  • โœ… All 791 tests passing with unified module/package model
  • โœ… All linting checks pass (ruff, mypy, complexity)
  • Phase 5 โ€” Scanner Integration & Full Test Coverage
  • โณ scan_package() method removed from PackageScanner
  • โณ scan_module(recursive=True) handles both packages and modules
  • โณ All test code migrated (no PackageHandle references)
  • โณ Full test suite passing, DI APIs unchanged

๐Ÿ“ 12. Changelog

  • 2025-10-26 โ€” Phase 4 completed: PackageHandle removal & API consolidation
  • Removed PackageHandle class entirely (deleted handles/package_handle.py)
  • Removed build_package() method from MemberBuilder
  • Updated resolve_package() to return ModuleHandle (validates path for package check)
  • Unified cache keys to ("module", qualname) pattern for all modules/packages
  • Removed MemberKind.MODULE and MemberKind.PACKAGE enum values
  • Refactored PackageScanner to dynamically discover submodules using pkgutil.iter_modules()
  • Updated ReflectionContext to check is_package property instead of PackageHandle type
  • Unified Python ImportResolver to use build_module() for both packages and modules
  • Updated all protocol definitions to use ModuleHandle instead of PackageHandle
  • All 791 tests passing, all linting checks pass
  • Unified module/package model simplifies reflection API
  • Phase 4 unblocks Phase 5 (scanner integration improvements)
  • 2025-10-26 โ€” Phase 3 completed: Ownership properties implemented
  • Added is_package: bool property to ModuleHandle (checks for __path__ attribute)
  • Added type: TypeHandle property to MethodHandle, FieldHandle, PropertyHandle, ConstructorHandle
  • Updated MemberBuilder to populate ownership properties during construction:
    • PropertyHandle receives declaring_type in _process_property_member()
    • FieldHandle receives declaring_type in _add_field_to_type() and _process_annotation_only_fields()
  • All 791 tests passing with ownership model in place
  • Explicit ownership chains now work: module โ†’ type โ†’ method/field/property
  • Phase 3 unblocks Phase C (Member Parsing Seam) implementation
  • 2025-10-26 โ€” Phase 1 completed; added Phase 2 terminology & restructuring
  • Phase 1 completed: ReflectionInfo/MemberInfo hierarchy established, 791 tests passing
  • Added section 2.4: Terminology decision (Handle vs Info/Ref/Proxy) with alternatives table
  • Added section 3.4: Package restructuring (handles package, one class per file)
  • Inserted Phase 2: Terminology change (*Info โ†’ *Handle) and package restructuring
  • Renumbered phases: ownership properties now Phase 3, removal now Phase 4, integration now Phase 5
  • Updated progress tracking to show Phase 1 completion
  • Updated migration strategy for 5-phase approach
  • 2025-10-25 โ€” Initial draft created (status ๐Ÿ”ต Draft)
  • Simplified two-level hierarchy: ReflectionInfo โ†’ ModuleInfo / MemberInfo
  • Explicit ownership semantics: module and type properties on MemberInfo
  • PackageInfo consolidated into ModuleInfo with is_package property
  • Four-phase implementation plan with clear phase boundaries defined
  • Integration strategy documented for Phase C unblocking