Skip to content

Commit 95c5541

Browse files
dugshubclaude
andcommitted
feat(core): add semantic types for wizard system
Implement core semantic types (BranchId, ActionId, OptionKey, MenuId) with factory functions, type guards, and StateValue for JSON-serializable data. - NewType definitions for type safety without runtime overhead - Factory functions with optional validation (default: no overhead) - Type guards for runtime type checking - Collection type aliases (BranchList, ActionSet, etc.) - StateValue type alias for JSON-serializable values - 100% test coverage with 28 comprehensive tests - MyPy strict mode compliance Part of CLI-4: Minimal Core Type Definitions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 69ebe9c commit 95c5541

File tree

2 files changed

+731
-2
lines changed

2 files changed

+731
-2
lines changed

src/cli_patterns/core/types.py

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,212 @@
1-
"""Core type definitions for CLI Patterns."""
1+
"""Core semantic types for the wizard system.
22
3-
# Placeholder for core type definitions
3+
This module defines semantic types that provide type safety for the wizard system
4+
while maintaining MyPy strict mode compliance. These are simple NewType definitions
5+
that prevent type confusion without adding runtime validation complexity.
6+
7+
The semantic types help distinguish between different string contexts in the wizard:
8+
- BranchId: Represents a branch identifier in the wizard tree
9+
- ActionId: Represents an action identifier
10+
- OptionKey: Represents an option key name
11+
- MenuId: Represents a menu identifier for navigation
12+
- StateValue: Represents any JSON-serializable value that can be stored in state
13+
14+
All ID types are backed by strings but provide semantic meaning at the type level.
15+
StateValue is a JSON-compatible type alias for flexible state storage.
16+
"""
17+
18+
from __future__ import annotations
19+
20+
from typing import Any, NewType, Union
21+
22+
from typing_extensions import TypeGuard
23+
24+
# JSON-compatible types for state values
25+
JsonPrimitive = Union[str, int, float, bool, None]
26+
JsonValue = Union[JsonPrimitive, list["JsonValue"], dict[str, "JsonValue"]]
27+
28+
# Core semantic types for wizard system
29+
BranchId = NewType("BranchId", str)
30+
"""Semantic type for branch identifiers in the wizard tree."""
31+
32+
ActionId = NewType("ActionId", str)
33+
"""Semantic type for action identifiers."""
34+
35+
OptionKey = NewType("OptionKey", str)
36+
"""Semantic type for option key names."""
37+
38+
MenuId = NewType("MenuId", str)
39+
"""Semantic type for menu identifiers."""
40+
41+
# State value is any JSON-serializable value
42+
StateValue = JsonValue
43+
"""Type alias for state values - any JSON-serializable data."""
44+
45+
# Type aliases for common collections using semantic types
46+
BranchList = list[BranchId]
47+
"""Type alias for lists of branch IDs."""
48+
49+
BranchSet = set[BranchId]
50+
"""Type alias for sets of branch IDs."""
51+
52+
ActionList = list[ActionId]
53+
"""Type alias for lists of action IDs."""
54+
55+
ActionSet = set[ActionId]
56+
"""Type alias for sets of action IDs."""
57+
58+
OptionDict = dict[OptionKey, StateValue]
59+
"""Type alias for option dictionaries mapping keys to state values."""
60+
61+
MenuList = list[MenuId]
62+
"""Type alias for lists of menu IDs."""
63+
64+
65+
# Factory functions for creating semantic types
66+
def make_branch_id(value: str, validate: bool = False) -> BranchId:
67+
"""Create a BranchId from a string value.
68+
69+
Args:
70+
value: String value to convert to BranchId
71+
validate: If True, validate the input (default: False for zero overhead)
72+
73+
Returns:
74+
BranchId with semantic type safety
75+
76+
Raises:
77+
ValueError: If validate=True and value is invalid
78+
"""
79+
if validate:
80+
if not value or not value.strip():
81+
raise ValueError("BranchId cannot be empty")
82+
if len(value) > 100:
83+
raise ValueError("BranchId is too long (max 100 characters)")
84+
return BranchId(value)
85+
86+
87+
def make_action_id(value: str, validate: bool = False) -> ActionId:
88+
"""Create an ActionId from a string value.
89+
90+
Args:
91+
value: String value to convert to ActionId
92+
validate: If True, validate the input (default: False for zero overhead)
93+
94+
Returns:
95+
ActionId with semantic type safety
96+
97+
Raises:
98+
ValueError: If validate=True and value is invalid
99+
"""
100+
if validate:
101+
if not value or not value.strip():
102+
raise ValueError("ActionId cannot be empty")
103+
if len(value) > 100:
104+
raise ValueError("ActionId is too long (max 100 characters)")
105+
return ActionId(value)
106+
107+
108+
def make_option_key(value: str, validate: bool = False) -> OptionKey:
109+
"""Create an OptionKey from a string value.
110+
111+
Args:
112+
value: String value to convert to OptionKey
113+
validate: If True, validate the input (default: False for zero overhead)
114+
115+
Returns:
116+
OptionKey with semantic type safety
117+
118+
Raises:
119+
ValueError: If validate=True and value is invalid
120+
"""
121+
if validate:
122+
if not value or not value.strip():
123+
raise ValueError("OptionKey cannot be empty")
124+
if len(value) > 100:
125+
raise ValueError("OptionKey is too long (max 100 characters)")
126+
return OptionKey(value)
127+
128+
129+
def make_menu_id(value: str, validate: bool = False) -> MenuId:
130+
"""Create a MenuId from a string value.
131+
132+
Args:
133+
value: String value to convert to MenuId
134+
validate: If True, validate the input (default: False for zero overhead)
135+
136+
Returns:
137+
MenuId with semantic type safety
138+
139+
Raises:
140+
ValueError: If validate=True and value is invalid
141+
"""
142+
if validate:
143+
if not value or not value.strip():
144+
raise ValueError("MenuId cannot be empty")
145+
if len(value) > 100:
146+
raise ValueError("MenuId is too long (max 100 characters)")
147+
return MenuId(value)
148+
149+
150+
# Type guard functions for runtime type checking
151+
def is_branch_id(value: Any) -> TypeGuard[BranchId]:
152+
"""Check if a value is a BranchId at runtime.
153+
154+
Args:
155+
value: Value to check
156+
157+
Returns:
158+
True if value is a BranchId (string type), False otherwise
159+
160+
Note:
161+
This is a type guard function that helps with type narrowing.
162+
At runtime, BranchId is just a string, so this checks for string type.
163+
"""
164+
return isinstance(value, str)
165+
166+
167+
def is_action_id(value: Any) -> TypeGuard[ActionId]:
168+
"""Check if a value is an ActionId at runtime.
169+
170+
Args:
171+
value: Value to check
172+
173+
Returns:
174+
True if value is an ActionId (string type), False otherwise
175+
176+
Note:
177+
This is a type guard function that helps with type narrowing.
178+
At runtime, ActionId is just a string, so this checks for string type.
179+
"""
180+
return isinstance(value, str)
181+
182+
183+
def is_option_key(value: Any) -> TypeGuard[OptionKey]:
184+
"""Check if a value is an OptionKey at runtime.
185+
186+
Args:
187+
value: Value to check
188+
189+
Returns:
190+
True if value is an OptionKey (string type), False otherwise
191+
192+
Note:
193+
This is a type guard function that helps with type narrowing.
194+
At runtime, OptionKey is just a string, so this checks for string type.
195+
"""
196+
return isinstance(value, str)
197+
198+
199+
def is_menu_id(value: Any) -> TypeGuard[MenuId]:
200+
"""Check if a value is a MenuId at runtime.
201+
202+
Args:
203+
value: Value to check
204+
205+
Returns:
206+
True if value is a MenuId (string type), False otherwise
207+
208+
Note:
209+
This is a type guard function that helps with type narrowing.
210+
At runtime, MenuId is just a string, so this checks for string type.
211+
"""
212+
return isinstance(value, str)

0 commit comments

Comments
 (0)