Skip to content

Commit 4a5af27

Browse files
committed
MAJOR FEATURE: wired in the Type_Safe__Config.skip_validation mode to the Type_Safe class
added tests that confirm the behaviour and issues with using skip_validation and fast_mode
1 parent c9bd1ca commit 4a5af27

File tree

4 files changed

+801
-1
lines changed

4 files changed

+801
-1
lines changed

osbot_utils/type_safe/Type_Safe.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ def __enter__(self): return self
2929
def __exit__(self, exc_type, exc_val, exc_tb): pass
3030

3131
def __setattr__(self, name, value):
32-
type_safe_step_set_attr.setattr(super(), self, name, value)
32+
config = get_active_config()
33+
if config and config.skip_validation:
34+
object.__setattr__(self, name, value)
35+
else:
36+
type_safe_step_set_attr.setattr(super(), self, name, value)
3337

3438
# def __setattr__(self, name, value):
3539
# from osbot_utils.type_safe.type_safe_core.config.static_methods.find_type_safe_config import find_type_safe_config
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
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

Comments
 (0)