📘 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.dottedtolerant/strict behavior with structured ResolveError payload - Simple scan filters supported consistently (
public_only,kindsuse MemberKind enum,name_contains) - Type hints facades implemented (
for_callable,for_type,for_field,for_property), preservingAnnotatedand PEP 604 unions -
annotations.for_memberfacade 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.constructto acceptTypeInfoand makectx.create.instancea thin alias to it; removeinvoke.create_instancefrom 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.dottedreturn shape (returns MemberInfo | None; strict default applies) - Define when
ScanErrorshould 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.