1+ # ═══════════════════════════════════════════════════════════════════════════════
2+ # Tests: Type_Safe Fast Create - Fast Create Side Effects
3+ # Documents the INTENTIONAL TRADE-OFFS when using fast_create=True
4+ # ═══════════════════════════════════════════════════════════════════════════════
5+ #
6+ # PURPOSE: Document what happens when objects are created with fast_create=True.
7+ # These are NOT BUGS - they are documented consequences of choosing
8+ # to bypass validation for performance.
9+ #
10+ # KEY DIFFERENCE FROM skip_validation:
11+ # - fast_create: Affects __init__ (kwargs bypass validation)
12+ # - skip_validation: Affects __setattr__ (post-creation assignments bypass validation)
13+ #
14+ # WHEN TO USE fast_create=True:
15+ # - Bulk loading from trusted sources (database, serialized data)
16+ # - Performance-critical object creation
17+ # - Data already validated elsewhere
18+ #
19+ # WHEN NOT TO USE fast_create=True:
20+ # - User input in constructor
21+ # - Untrusted external data
22+ # - When type safety during construction is critical
23+ #
24+ # ═══════════════════════════════════════════════════════════════════════════════
25+
26+ from typing import Dict , List
27+ from unittest import TestCase
28+ from osbot_utils .type_safe .Type_Safe import Type_Safe
29+ from osbot_utils .type_safe .type_safe_core .config .Type_Safe__Config import Type_Safe__Config
30+ from osbot_utils .type_safe .type_safe_core .fast_create .Type_Safe__Fast_Create__Cache import type_safe_fast_create_cache
31+
32+
33+ # ═══════════════════════════════════════════════════════════════════════════════
34+ # Test Classes
35+ # ═══════════════════════════════════════════════════════════════════════════════
36+
37+ class TS__Person (Type_Safe ):
38+ name : str = ''
39+ age : int = 0
40+
41+
42+ class TS__With_List (Type_Safe ):
43+ name : str = ''
44+ items : List [str ]
45+
46+
47+ class TS__With_Dict (Type_Safe ):
48+ name : str = ''
49+ data : Dict [str , int ]
50+
51+
52+ class TS__Nested_Inner (Type_Safe ):
53+ value : str = ''
54+
55+
56+ class TS__With_Nested (Type_Safe ):
57+ inner : TS__Nested_Inner
58+ name : str = ''
59+
60+
61+ class TS__With_Defaults (Type_Safe ):
62+ name : str = 'default_name'
63+ count : int = 42
64+ enabled : bool = True
65+
66+
67+ # ═══════════════════════════════════════════════════════════════════════════════
68+ # Test: Fast Create Side Effects (Documented Trade-offs)
69+ # ═══════════════════════════════════════════════════════════════════════════════
70+
71+ class test_Type_Safe__Fast_Create__fast_create_side_effects (TestCase ):
72+ """
73+ These tests document the EXPECTED CONSEQUENCES of using fast_create=True.
74+ All tests PASS - they document behavior, not bugs.
75+
76+ Key insight: fast_create affects CONSTRUCTOR (kwargs), not post-creation setattr.
77+ """
78+
79+ def setUp (self ):
80+ type_safe_fast_create_cache .clear_cache ()
81+
82+ # ───────────────────────────────────────────────────────────────────────────
83+ # Side Effect: kwargs Not Validated During Construction
84+ # ───────────────────────────────────────────────────────────────────────────
85+
86+ def test__side_effect__kwargs_wrong_type_accepted (self ): # Constructor accepts wrong types
87+ with Type_Safe__Config (fast_create = True ):
88+ obj = TS__Person (name = 12345 , age = 'twenty' ) # Wrong types in kwargs!
89+
90+ assert obj .name == 12345 # int instead of str
91+ assert obj .age == 'twenty' # str instead of int
92+
93+ def test__side_effect__kwargs_none_to_non_optional (self ): # None accepted for required fields
94+ with Type_Safe__Config (fast_create = True ):
95+ obj = TS__Person (name = None , age = None )
96+
97+ assert obj .name is None
98+ assert obj .age is None
99+
100+ def test__side_effect__kwargs_complex_types_accepted (self ): # Any object accepted
101+ with Type_Safe__Config (fast_create = True ):
102+ obj = TS__Person (name = {'complex' : 'dict' }, age = [1 , 2 , 3 ])
103+
104+ assert obj .name == {'complex' : 'dict' }
105+ assert obj .age == [1 , 2 , 3 ]
106+
107+ # ───────────────────────────────────────────────────────────────────────────
108+ # Side Effect: kwargs Not Converted During Construction
109+ # ───────────────────────────────────────────────────────────────────────────
110+
111+ def test__side_effect__kwargs_not_auto_converted (self ): # No type coercion
112+ # In normal mode, some conversions might happen
113+ # In fast_create mode, values are used as-is
114+
115+ with Type_Safe__Config (fast_create = True ):
116+ obj = TS__Person (name = 12345 ) # int, not converted to '12345'
117+
118+ assert obj .name == 12345 # Still int!
119+ assert type (obj .name ) is int # Not converted to str
120+
121+ # ───────────────────────────────────────────────────────────────────────────
122+ # Side Effect: Collections in kwargs Used As-Is
123+ # ───────────────────────────────────────────────────────────────────────────
124+
125+ def test__side_effect__list_kwarg_wrong_element_types (self ): # List elements not validated
126+ with Type_Safe__Config (fast_create = True ):
127+ obj = TS__With_List (items = [1 , 2 , 3 , {'key' : 'val' }]) # Not List[str]!
128+
129+ assert obj .items == [1 , 2 , 3 , {'key' : 'val' }]
130+
131+ def test__side_effect__list_kwarg_not_a_list (self ): # Can pass non-list to list field
132+ with Type_Safe__Config (fast_create = True ):
133+ obj = TS__With_List (items = 'not_a_list' )
134+
135+ assert obj .items == 'not_a_list'
136+ assert type (obj .items ) is str
137+
138+ def test__side_effect__dict_kwarg_wrong_types (self ): # Dict types not validated
139+ with Type_Safe__Config (fast_create = True ):
140+ obj = TS__With_Dict (data = {123 : 'wrong' , 'key' : [1 , 2 , 3 ]}) # Wrong key and value types
141+
142+ assert obj .data == {123 : 'wrong' , 'key' : [1 , 2 , 3 ]}
143+
144+ # ───────────────────────────────────────────────────────────────────────────
145+ # Side Effect: Nested Object kwargs Not Validated
146+ # ───────────────────────────────────────────────────────────────────────────
147+
148+ def test__side_effect__nested_kwarg_can_be_dict (self ): # Dict instead of Type_Safe object
149+ with Type_Safe__Config (fast_create = True ):
150+ obj = TS__With_Nested (inner = {'value' : 'from_dict' }) # Dict, not TS__Nested_Inner
151+
152+ assert obj .inner == {'value' : 'from_dict' }
153+ assert type (obj .inner ) is dict # Not TS__Nested_Inner!
154+
155+ def test__side_effect__nested_kwarg_can_be_string (self ): # String instead of Type_Safe object
156+ with Type_Safe__Config (fast_create = True ):
157+ obj = TS__With_Nested (inner = 'just_a_string' )
158+
159+ assert obj .inner == 'just_a_string'
160+ assert type (obj .inner ) is str
161+
162+ def test__side_effect__nested_kwarg_can_be_none (self ): # None instead of Type_Safe object
163+ with Type_Safe__Config (fast_create = True ):
164+ obj = TS__With_Nested (inner = None )
165+
166+ assert obj .inner is None
167+
168+ # ───────────────────────────────────────────────────────────────────────────
169+ # Side Effect: Defaults Still Work When No kwarg Provided
170+ # ───────────────────────────────────────────────────────────────────────────
171+
172+ def test__side_effect__defaults_used_when_no_kwarg (self ): # Defaults still apply
173+ with Type_Safe__Config (fast_create = True ):
174+ obj = TS__With_Defaults () # No kwargs
175+
176+ assert obj .name == 'default_name' # Default applied
177+ assert obj .count == 42 # Default applied
178+ assert obj .enabled is True # Default applied
179+
180+ def test__side_effect__partial_kwargs_mixed_with_defaults (self ): # Some kwargs, some defaults
181+ with Type_Safe__Config (fast_create = True ):
182+ obj = TS__With_Defaults (name = 'custom' ) # Only override name
183+
184+ assert obj .name == 'custom' # From kwargs
185+ assert obj .count == 42 # From default
186+ assert obj .enabled is True # From default
187+
188+ # ───────────────────────────────────────────────────────────────────────────
189+ # Side Effect: Unknown kwargs May Be Accepted
190+ # ───────────────────────────────────────────────────────────────────────────
191+
192+ def test__side_effect__unknown_kwargs_accepted (self ): # Extra fields added to __dict__
193+ with Type_Safe__Config (fast_create = True ):
194+ obj = TS__Person (name = 'test' , age = 30 , unknown_field = 'extra' )
195+
196+ assert obj .name == 'test'
197+ assert obj .age == 30
198+ assert obj .unknown_field == 'extra' # Extra field exists!
199+ assert 'unknown_field' in obj .__dict__
200+
201+ def test__side_effect__unknown_kwargs_not_in_schema (self ): # But not in json() if not annotated
202+ with Type_Safe__Config (fast_create = True ):
203+ obj = TS__Person (name = 'test' , extra = 'field' )
204+
205+ # Extra field is in __dict__ but json() only serializes annotated fields
206+ assert obj .extra == 'field'
207+ json_data = obj .json ()
208+ # Note: behavior depends on json() implementation - it may or may not include extra
209+
210+ # ───────────────────────────────────────────────────────────────────────────
211+ # Side Effect: Post-Creation Validation Still Works (Without skip_validation)
212+ # ───────────────────────────────────────────────────────────────────────────
213+
214+ def test__side_effect__setattr_still_validates_after_fast_create (self ): # Only __init__ bypassed
215+ with Type_Safe__Config (fast_create = True ): # Note: skip_validation=False (default)
216+ obj = TS__Person (name = 12345 ) # This works (fast_create)
217+
218+ # Object has invalid state from construction
219+ assert obj .name == 12345
220+
221+ # But __setattr__ still validates (skip_validation not set)
222+ try :
223+ obj .name = 67890 # Another invalid value
224+ raised_error = False
225+ except (TypeError , ValueError ):
226+ raised_error = True
227+
228+ assert raised_error is True # Validation active on setattr!
229+
230+ def test__side_effect__can_fix_via_setattr (self ): # Can repair invalid state
231+ with Type_Safe__Config (fast_create = True ):
232+ obj = TS__Person (name = 12345 ) # Invalid from constructor
233+
234+ # Fix by setting valid value (validation allows valid values)
235+ obj .name = 'now_valid'
236+
237+ assert obj .name == 'now_valid'
238+ assert type (obj .name ) is str
239+
240+ # ───────────────────────────────────────────────────────────────────────────
241+ # Side Effect: json() With Invalid Constructor Data
242+ # ───────────────────────────────────────────────────────────────────────────
243+
244+ def test__side_effect__json_serializes_invalid_kwargs (self ): # json() includes whatever is there
245+ with Type_Safe__Config (fast_create = True ):
246+ obj = TS__Person (name = 12345 , age = 'twenty' )
247+
248+ json_data = obj .json ()
249+
250+ assert json_data ['name' ] == 12345 # int in JSON
251+ assert json_data ['age' ] == 'twenty' # str in JSON
252+
253+ def test__side_effect__json_handles_non_serializable (self ): # Non-serializable becomes None
254+ with Type_Safe__Config (fast_create = True ):
255+ obj = TS__Person (name = object ()) # object() not JSON serializable
256+
257+ json_data = obj .json ()
258+ assert json_data == {'age' : 0 , 'name' : None } # object() becomes None
259+
260+ # ───────────────────────────────────────────────────────────────────────────
261+ # Comparison: Normal Mode vs Fast Create Mode
262+ # ───────────────────────────────────────────────────────────────────────────
263+
264+ def test__comparison__normal_mode_validates_kwargs (self ): # Normal mode rejects bad kwargs
265+ try :
266+ obj = TS__Person (name = 12345 ) # No fast_create context
267+ raised_error = False
268+ except (TypeError , ValueError ):
269+ raised_error = True
270+
271+ assert raised_error is True # Normal mode validates!
272+
273+ def test__comparison__fast_create_accepts_same_kwargs (self ): # Fast create accepts bad kwargs
274+ with Type_Safe__Config (fast_create = True ):
275+ obj = TS__Person (name = 12345 ) # Same kwargs
276+
277+ assert obj .name == 12345 # Accepted in fast_create
278+
279+ # ───────────────────────────────────────────────────────────────────────────
280+ # Documented Safe Pattern: Trusted Data Loading
281+ # ───────────────────────────────────────────────────────────────────────────
282+
283+ def test__safe_pattern__constructor_with_trusted_data (self ): # Intended use case
284+ # Simulate data from trusted source
285+ trusted_records = [
286+ {'name' : 'Alice' , 'age' : 30 },
287+ {'name' : 'Bob' , 'age' : 25 },
288+ ]
289+
290+ with Type_Safe__Config (fast_create = True ):
291+ people = [TS__Person (** record ) for record in trusted_records ]
292+
293+ assert len (people ) == 2
294+ assert people [0 ].name == 'Alice'
295+ assert people [0 ].age == 30
296+ assert type (people [0 ].name ) is str # Correct because source was valid
297+ assert type (people [0 ].age ) is int
298+
299+ def test__safe_pattern__from_json_roundtrip (self ): # Deserializing own data
300+ # Create and serialize valid object
301+ original = TS__Person (name = 'Test' , age = 42 )
302+ json_data = original .json ()
303+
304+ # Fast reconstruct from JSON
305+ with Type_Safe__Config (fast_create = True ):
306+ loaded = TS__Person (** json_data )
307+
308+ assert loaded .name == original .name
309+ assert loaded .age == original .age
0 commit comments