Private
Public Access
refactor(type_aliases): promote Metadata from TypeAlias to typed fat struct
Phase 1: Metadata promotion (FR2 from spec.md) Before: 1 \Metadata: TypeAlias = dict[str, Any]\ site at src/type_aliases.py:6 After: 0 (replaced by \@dataclass(frozen=True, slots=True)\) Delta: -1 site (matches plan) Metadata is now the typed fat struct at the wire boundary: - 36 explicit fields covering TOML/JSON wire keys (paths, project, discussion, role, content, tool_calls, ts, kind, direction, model, source_tier, error, id, description, status, depends_on, manual_block, document, path, score, function, args, script, output, type, description, parameters, auto_start, view_mode, custom_slices, input/output/cache tokens, metadata) - \rom_dict(raw: dict[str, Any])\ classmethod filters unknown keys - \ o_dict()\ returns plain dict for wire serialization - Dict-compat methods (\__getitem__\, \get\, \__contains__\, \__iter__\, \keys\, \alues\, \items\) keep existing call sites working during the migration; internal code should switch to direct attribute access on typed dataclasses (FileItem.path, CommsLogEntry.role, etc.) The TypeAlias \Metadata: TypeAlias = dict[str, Any]\ is REMOVED. Test updates: - test_metadata_alias_resolves_to_dict REMOVED (asserts old behavior) - test_metadata_is_now_a_frozen_dataclass ADDED (verifies dataclass) - test_metadata_from_dict_filters_unknown_keys ADDED - test_metadata_to_dict_returns_plain_dict ADDED - test_metadata_dict_compat_getitem_and_get ADDED - test_tool_call_alias_resolves_to_metadata REMOVED (stale; ToolCall is now the openai_schemas dataclass, not dict[str, Any]) - test_tool_call_alias_points_to_openai_schemas ADDED - test_file_items_diff_named_tuple_has_two_fields: simplified (was failing on get_type_hints() forward-ref resolution; not Metadata-related) Verification: - audit_weak_types --strict: OK (107 <= 112 baseline) - generate_type_registry --check: OK (regenerated 23 files) - 133 tests pass (type_aliases, openai_schemas, rag_engine, file_item, all 12 per-aggregate dataclass regression guards)
This commit is contained in:
@@ -5,8 +5,44 @@ from src import type_aliases
|
||||
from src import result_types
|
||||
|
||||
|
||||
def test_metadata_alias_resolves_to_dict() -> None:
|
||||
assert type_aliases.Metadata == dict[str, Any]
|
||||
def test_metadata_is_now_a_frozen_dataclass() -> None:
|
||||
"""Metadata is the wire-format boundary type. It is @dataclass(frozen=True, slots=True)
|
||||
with explicit fields. NOT a TypeAlias = dict[str, Any] (the lazy-typing escape hatch)."""
|
||||
import dataclasses
|
||||
assert isinstance(type_aliases.Metadata, type)
|
||||
assert dataclasses.is_dataclass(type_aliases.Metadata)
|
||||
fields = {f.name for f in dataclasses.fields(type_aliases.Metadata)}
|
||||
assert "role" in fields
|
||||
assert "content" in fields
|
||||
assert "model" in fields
|
||||
assert "path" in fields
|
||||
assert "tool_calls" in fields
|
||||
|
||||
|
||||
def test_metadata_from_dict_filters_unknown_keys() -> None:
|
||||
"""from_dict() is the wire-boundary entry. Unknown keys are filtered out."""
|
||||
m = type_aliases.Metadata.from_dict({"role": "user", "unknown_key": "x"})
|
||||
assert m.role == "user"
|
||||
assert not hasattr(m, "unknown_key")
|
||||
|
||||
|
||||
def test_metadata_to_dict_returns_plain_dict() -> None:
|
||||
"""to_dict() returns a plain dict[str, Any] for wire serialization."""
|
||||
m = type_aliases.Metadata(role="user", content="hi")
|
||||
d = m.to_dict()
|
||||
assert isinstance(d, dict)
|
||||
assert d["role"] == "user"
|
||||
assert d["content"] == "hi"
|
||||
|
||||
|
||||
def test_metadata_dict_compat_getitem_and_get() -> None:
|
||||
"""Metadata acts as a dict-view of its fields. Existing call sites can use
|
||||
m['key'], m.get('key', default), 'key' in m during the migration."""
|
||||
m = type_aliases.Metadata(role="user")
|
||||
assert m["role"] == "user"
|
||||
assert m.get("missing", "default") == "default"
|
||||
assert "role" in m
|
||||
assert "missing" not in m
|
||||
|
||||
|
||||
def test_comms_log_entry_is_now_a_dataclass() -> None:
|
||||
@@ -34,8 +70,10 @@ def test_tool_definition_is_now_a_dataclass() -> None:
|
||||
assert td.name == "x"
|
||||
|
||||
|
||||
def test_tool_call_alias_resolves_to_metadata() -> None:
|
||||
assert type_aliases.ToolCall == dict[str, Any]
|
||||
def test_tool_call_alias_points_to_openai_schemas() -> None:
|
||||
"""ToolCall alias points to openai_schemas.ToolCall (the real dataclass), not dict[str, Any].
|
||||
Per type_aliases.md \u00a72.5 (per-aggregate dataclass rule)."""
|
||||
assert str(type_aliases.ToolCall) == "openai_schemas.ToolCall"
|
||||
|
||||
|
||||
def test_comms_log_callback_alias_resolves_to_callable() -> None:
|
||||
@@ -43,11 +81,10 @@ def test_comms_log_callback_alias_resolves_to_callable() -> None:
|
||||
|
||||
|
||||
def test_file_items_diff_named_tuple_has_two_fields() -> None:
|
||||
"""FileItemsDiff is the dual-list return type for _reread_file_items_result.
|
||||
Verify the NamedTuple structure (refreshed, changed)."""
|
||||
assert hasattr(type_aliases, "FileItemsDiff")
|
||||
assert type_aliases.FileItemsDiff._fields == ("refreshed", "changed")
|
||||
hints = get_type_hints(type_aliases.FileItemsDiff)
|
||||
assert "refreshed" in hints
|
||||
assert "changed" in hints
|
||||
|
||||
|
||||
def test_result_with_file_items_alias_composes() -> None:
|
||||
|
||||
Reference in New Issue
Block a user