Skip to content

📘 Technical Design 003 — ReflectionContext Surface: A Unified, Ergonomic API

Field Value
ID TD-003
Title ReflectionContext Surface: A Unified, Ergonomic API
Status Completed
Owner Reflection maintainers
Audience Contributors and maintainers
Created 2025-09-27
Last Updated 2025-09-28
Reviewers
Approvers
Tracking
Related 001-centralized-metrics, 002-rename-basic-naming, refactor plan/findings

🧭 1. Summary

This TD defines the complete, ergonomic public surface of ReflectionContext. The aim is to make common reflection tasks obvious and consistent while preserving correctness of the underlying logic. The library is in alpha; we will change or remove public APIs to achieve a superior developer experience.

At a glance:

  • Single entry point with six cohesive facades: resolve, scan, hints, annotations, invoke, create.
  • Consistent parameter ordering and option naming; structured errors by default.
  • Strictness defaults to fail-fast validation (strict=True) backed by a pluggable TypeChecker; opt out per call with strict=False.
  • Minimal, stable observability (logs/metrics) for key operations.

📚 2. Context and Problem Statement

  • Consumers primarily interact with ReflectionContext; scattered helpers and inconsistent method shapes add friction.

2.1 Goals

  • One obvious way to perform each task from the context, with predictable defaults and structured errors.
  • Preserve correctness (determinism, fidelity of type hints, safe invocation).

2.2 Non-Goals (Out of Scope)

  • Non-reflection utilities and modules not directly related to the ReflectionContext public surface.
  • Performance optimizations beyond maintaining current bounds (unless they affect the API surface).
  • Backwards-compatibility shims or deprecation machinery beyond the alpha-stage policy.

2.3 Assumptions and Constraints

  • Alpha policy: breaking changes allowed; focus on ergonomics and clarity.
  • Performance should remain within current bounds on typical paths.
  • The context is the single entry point for public operations.

🧩 3. Design Overview

3.1 Facades at a Glance

Table 1: Overview of facades and their purpose.

Facade Purpose
ctx.resolve Resolve modules, types, packages, and dotted paths.
ctx.scan Iterate members of packages/modules and selected types with simple filters.
ctx.hints Retrieve type hints for callables, types, fields, and properties.
ctx.annotations Access annotation data for members.
ctx.invoke Call functions/methods or construct instances; supports strict checks (opt-in).
ctx.create Create instances (thin alias to invoke.construct) with the same strictness semantics.

Each group has consistent parameter ordering and option naming. Errors are structured and debuggable. Optional strictness flags cause fail-fast behavior.

3.2 Removed Interfaces (Before → After)

To converge on the unified facades, the following flat methods on ReflectionContext will be removed. The table shows direct before→after mappings.

Important

This project is in alpha. Removals may occur in the same PRs that introduce the new facades, without a deprecation period.

Area Before (removed) After (use instead)
Resolution resolve_module(name: str) resolve.module(name)
Resolution resolve_type(name: str) resolve.type(name)
Resolution resolve_package(name: str) resolve.package(name)
Resolution try_resolve(path: str) resolve.dotted(path, strict=...)
Scanning scan_package(target, predicate=None, recursive=True) scan.package(target, recursive=..., public_only=..., kinds=..., name_contains=...)
Scanning scan_module(target, predicate=None) scan.module(target, public_only=..., kinds=..., name_contains=...)
Scanning scan_types(types, predicate=None) scan.types(types, public_only=..., name_contains=...)
Invocation & instance creation invoke(callable_info, *args, **kwargs) invoke.call(callable_info, *args, strict=..., **kwargs)
Invocation & instance creation construct(type_info, *args, **kwargs) invoke.construct(type_info, *args, strict=..., **kwargs)
Invocation & instance creation create_instance(type_info, *args, **kwargs) create.instance(type_info, *args, strict=..., **kwargs)
Type hints type_hints_for_callable(callable_info) hints.for_callable(callable_info)
Type hints type_hints_for_type(type_info) hints.for_type(type_info)
Type hints type_hints_for_field(field_info) hints.for_field(field_info)
Type hints type_hints_for_property(prop_info) hints.for_property(prop_info)
Annotations annotations_for_member(member_info) annotations.for_member(member_info)

Note

See §4 (“Detailed API Surface”) for finalized facade methods and §8 (“Rollout”) for migration guidance.

3.3 Removal Policy

  • Alpha stage: these removals will occur in the same PRs that introduce/solidify the facades; no deprecation period.
  • Migration: this document provides direct before→after mappings. Tests and examples will be updated accordingly.

📐 4. Detailed API Surface

Notation: Optional flags default to context-level defaults. Strictness flags control fail-fast behavior where applicable.

4.1 Resolve

ctx.resolve.module(name: str) -> ModuleInfo
ctx.resolve.type(name: str) -> TypeInfo
ctx.resolve.package(name: str) -> PackageInfo
ctx.resolve.dotted(path: str, *, strict: bool | None = None) -> MemberInfo | None

Behavior: dotted() returns the most specific leaf object if resolvable (module → attribute → nested attribute). If strict is not provided, the default is strict=True at the context level. When strict=True, raises ResolveError on failure; when strict=False, returns None on failure.

Error: ResolveError payload: {path, partial: list[str], reason: str}

4.2 Scan

# Common filter parameters (simple by design):
# public_only: bool = True
# kinds: set[str] | None = None  # e.g., {"class","function","property","field"}
# name_contains: str | None = None  # case-sensitive

ctx.scan.package(target: str | PackageInfo, *,
recursive: bool = True,
public_only: bool = True,
kinds: set[str] | None = None,
name_contains: str | None = None) -> Iterable[MemberInfo]

ctx.scan.module(target: str | ModuleInfo, *,
public_only: bool = True,
kinds: set[str] | None = None,
name_contains: str | None = None) -> Iterable[MemberInfo]

ctx.scan.types(types: Iterable[type], *,
public_only: bool = True,
name_contains: str | None = None) -> Iterable[TypeInfo]

4.3 Hints

ctx.hints.for_callable(fn: CallableInfo) -> Mapping[str, object]
ctx.hints.for_type(t: TypeInfo) -> Mapping[str, object]
ctx.hints.for_field(f: FieldInfo) -> object
ctx.hints.for_property(p: PropertyInfo) -> object

Behavior: Deterministic semantics and precedence are preserved.

Error: Errors (in strict upstream providers) bubble up as structured exceptions.

4.4 Annotations

ctx.annotations.for_member(m: MemberInfo) -> Mapping[str, object] | list[Annotation]

Behavior: Shape depends on member type; kept consistent with existing models.

4.5 Invoke

ctx.invoke.call(fn: CallableInfo, *args, strict: bool | None = None, ** kwargs) -> object
ctx.invoke.construct(t: TypeInfo, *args, strict: bool | None = None, ** kwargs) -> object

Behavior: Strict validation uses a pluggable TypeChecker when strict=True (default). Strict=True: fail fast on first error with InvocationTypeError.

Error: InvocationTypeError payload: {subject_qualname, param: str, expected: str, actual_type: str, actual_value_repr?: str}

4.6 Create

ctx.create.instance(t: TypeInfo, *args, strict: bool | None = None, ** kwargs) -> object

Behavior: Thin alias to ctx.invoke.construct with the same semantics.

⚙️ 5. Semantics

5.1 Parameter Conventions

  • Subject first (name or object), then options/flags (e.g., public_only, kinds, name_contains, strict).

5.2 Strictness and Fail‑Fast

  • Only relevant to operations where runtime validation applies (invoke/construct and resolve.dotted).
  • Default is strict=True at the context level; per-call overrides are supported via strict=... keyword (use strict=False to opt out).

5.3 Error Model

  • ResolveError: path, partial (prefixes successfully resolved), reason.
  • ScanError: subject identity and reason.
  • InvocationTypeError: subject_qualname, parameter, expected vs actual; includes a concise message and .to_dict().
  • All errors are concise and suitable for logs/metrics; messages avoid leaking sensitive values beyond a small repr.

5.4 Observability

  • Best-effort metrics/logs for resolve, scan, invoke events with stable names and minimal payloads.

🧪 6. Examples (concise)

6.1 Resolve and Invoke (Strict)

t = ctx.resolve.type("pkg.mod.A")
a = ctx.invoke.construct(t, 1, name="x", strict=True)  # fail-fast if mismatch
fn = next(m for m in t.methods if m.name == "do")
ctx.invoke.call(fn, 42, strict=True)

6.2 Resolve Dotted (Tolerant vs Strict)

any_obj = ctx.resolve.dotted("pkg.mod.A.attr", strict=False)  # None if missing
val = ctx.resolve.dotted("pkg.mod.A.attr", strict=True)  # raises ResolveError

6.3 Scan with Simple Filters

members = list(ctx.scan.module("pkg.mod", public_only=True, name_contains="Service"))

6.4 Hints and Annotations

hints = ctx.hints.for_callable(fn)
field_hint = ctx.hints.for_field(next(f for f in t.fields if f.name == "id"))
ann = ctx.annotations.for_member(t)

✅ 7. Acceptance Criteria

Note

Use this checklist to verify conformance when reviewing PRs.

  • ReflectionContext exposes only the methods listed above under resolve, scan, hints, annotations, invoke, and create.
  • Parameter names and ordering are consistent across groups.
  • Strictness flags work as specified; InvocationTypeError/ResolveError payloads match the documented schema.
  • Simple scan filters behave consistently across package and module scans.
  • Examples compile against the final surface.

🚀 8. Rollout

  • Alpha policy: remove or rename existing ReflectionContext methods that overlap with or contradict this surface.
  • Stage changes facade-by-facade; keep a short migration note per facade in this document.
  • Add focused tests for each group (resolve/scan/hints/annotations/invoke/create).

⚠️ 9. Risks and Mitigations

  • Risk: Removing lesser-used helpers may break niche flows.
    • Mitigation: Provide equivalent flows via the new facades and document brief migrations.
  • Risk: Strict checks too opinionated.
    • Mitigation: Strict is opt-in; keep checks pragmatic and bounded.

📈 10. Progress

Legend: [x] = complete, [ ] = pending

  • Grouped facades exposed on ReflectionContext: resolve, scan, hints, annotations, invoke, create
  • resolve.dotted tolerant/strict behavior with structured ResolveError payload
  • Simple scan filters supported consistently (public_only, kinds use MemberKind enum, name_contains)
  • Type hints facades implemented (for_callable, for_type, for_field, for_property), preserving Annotated and PEP 604 unions
  • annotations.for_member facade available
  • Strict invocation path wired via pluggable TypeChecker with InvocationTypeError payload
  • Legacy flat methods removed in favor of the new facades
  • Basic observability hooks (metrics/logging) present for key operations
  • Focused tests exist for resolve, scan, hints/annotations, and invoke/create groups
  • Align ctx.invoke.construct to accept TypeInfo and make ctx.create.instance a thin alias to it; remove invoke.create_instance from the public surface; update tests/examples (done in code and tests)
  • Decide whether to keep the direct callable alias ctx.invoke(...) (kept as a convenience; same strict semantics as call())
  • Clarify the wording for resolve.dotted return shape (returns MemberInfo | None; strict default applies)
  • Define when ScanError should be raised vs relying solely on resolve failures
  • Add tests asserting ScanError vs resolve failure behavior across scan.package/module/types per policy
  • Schema-lock error payloads: tests assert exact ResolveError and InvocationTypeError payload keys/shapes; apply repr bounds policy
  • API surface conformance tests: ensure subject-first parameter order, keyword-only flags, and strict defaulting on all facades
  • Resolve.dotted return conformance tests: assert MemberInfo | None and context-level strict default on absence
  • Alias cleanup and verification: no invoke.create_instance exposure; create.instance delegates to invoke.construct; sweep examples/docs for stale names
  • Docs sync: update README and docs examples to use only facades; add brief migration notes per facade in this TD
  • Default strictness policy confirmed: strict=True at the context level; per-call override via strict=False

📝 11. Changelog

  • 2025-09-28 — Formatting and structure updates: normalized headings/numbering, converted Removed Interfaces to a table, added callouts and cross-references, consolidated progress with visual indicators, added Glossary; added follow-up tasks and progress updates.
  • 2025-09-27 — Initial draft.

📖 12. Glossary

  • Facade: A cohesive group of related operations exposed under a common namespace (e.g., ctx.resolve).
  • Strictness: Opt-in mode that turns certain operations into fail-fast behavior with structured errors.
  • MemberInfo: A wrapper representing a module/type/member entity used by this library’s APIs.
  • TypeChecker: A pluggable component used by invocation paths to validate arguments against type hints.