From 4e658dd25c663c66a838dfb7b785c0f69af49045 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 21 Jun 2026 15:57:40 -0400 Subject: [PATCH] feat(types): add JsonPrimitive + JsonValue TypeAliases (t0_3) Phase 0 of any_type_componentization_20260621. Extends src/type_aliases.py with two recursive-friendly TypeAliases for JSON wire format (used by Phase 5 api_hooks WebSocketMessage): - JsonPrimitive: str | int | float | bool | None - JsonValue: JsonPrimitive | list['JsonValue'] | dict[str, 'JsonValue'] The forward-ref 'JsonValue' strings work because from __future__ import annotations is at the top of the module (PEP 563 + PEP 613 TypeAlias). Tests added (4 new, 14 total): - test_json_primitive_alias_resolves_to_union: hints exposes JsonPrimitive - test_json_value_alias_resolves_to_recursive_union: hints exposes JsonValue - test_json_value_accepts_primitive_dict: dict[str, JsonValue] runtime use - test_json_value_accepts_nested_structures: nested dict+list round-trip Verification: uv run pytest tests/test_type_aliases.py --timeout=30 14 passed in 2.97s --- src/type_aliases.py | 3 +++ tests/test_type_aliases.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/type_aliases.py b/src/type_aliases.py index 181a8232..ecb80e75 100644 --- a/src/type_aliases.py +++ b/src/type_aliases.py @@ -18,6 +18,9 @@ ToolCall: TypeAlias = Metadata CommsLogCallback: TypeAlias = Callable[[CommsLogEntry], None] +JsonPrimitive: TypeAlias = str | int | float | bool | None +JsonValue: TypeAlias = JsonPrimitive | list["JsonValue"] | dict[str, "JsonValue"] + class FileItemsDiff(NamedTuple): refreshed: FileItems diff --git a/tests/test_type_aliases.py b/tests/test_type_aliases.py index 2890100b..245f139f 100644 --- a/tests/test_type_aliases.py +++ b/tests/test_type_aliases.py @@ -49,4 +49,36 @@ def test_file_items_diff_named_tuple_has_two_fields() -> None: def test_result_with_file_items_alias_composes() -> None: r: result_types.Result[type_aliases.FileItems] = result_types.Result(data=[]) assert r.ok is True - assert isinstance(r.data, list) \ No newline at end of file + assert isinstance(r.data, list) + + +def test_json_primitive_alias_resolves_to_union() -> None: + assert hasattr(type_aliases, "JsonPrimitive") + hints = get_type_hints(type_aliases) + assert "JsonPrimitive" in hints + + +def test_json_value_alias_resolves_to_recursive_union() -> None: + assert hasattr(type_aliases, "JsonValue") + hints = get_type_hints(type_aliases) + assert "JsonValue" in hints + jv = hints["JsonValue"] + assert jv is not None + + +def test_json_value_accepts_primitive_dict() -> None: + payload: type_aliases.JsonValue = {"key": "value", "count": 42, "active": True, "nothing": None} + assert payload["key"] == "value" + assert payload["count"] == 42 + assert payload["active"] is True + assert payload["nothing"] is None + + +def test_json_value_accepts_nested_structures() -> None: + payload: type_aliases.JsonValue = { + "users": [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}], + "metadata": {"source": "test", "tags": ["a", "b", "c"]}, + } + assert len(payload["users"]) == 2 + assert payload["users"][0]["name"] == "alice" + assert payload["metadata"]["tags"][1] == "b" \ No newline at end of file