@@ -60,29 +60,75 @@ def actual_instance_must_validate_anyof(cls, v):
6060
6161 instance = Entity .model_construct ()
6262 error_messages = []
63- # validate data type: Entity (or dict that can become Entity)
64- if isinstance (v , Entity ):
65- return v
66-
67- # Check if it's a dict (Entity-like object)
68- if isinstance (v , dict ):
63+ # validate data type: Entity
64+ if not isinstance (v , Entity ):
65+ error_messages .append (f"Error! Input type `{ type (v )} ` is not `Entity`" )
66+ else :
6967 return v
7068
7169 # validate data type: int
72- if isinstance (v , int ):
73- try :
74- instance .anyof_schema_2_validator = v
75- return v
76- except (ValidationError , ValueError ) as e :
77- error_messages .append (str (e ))
78- else :
79- error_messages .append (f"Error! Input type `{ type (v )} ` is not `Entity`, `dict`, or `int`" )
80-
70+ try :
71+ instance .anyof_schema_2_validator = v
72+ return v
73+ except (ValidationError , ValueError ) as e :
74+ error_messages .append (str (e ))
8175 if error_messages :
8276 # no match
8377 raise ValueError ("No match found when setting the actual_instance in Entity with anyOf schemas: Entity, int. Details: " + ", " .join (error_messages ))
8478 else :
8579 return v
80+ def __getattr__ (self , name : str ):
81+ """Delegate attribute access to actual_instance for backward compatibility.
82+
83+ This allows code like `tx.height` instead of `tx.actual_instance.height`.
84+ """
85+ if name .startswith ('_' ) or name in (
86+ 'actual_instance' , 'one_of_schemas' , 'model_config' ,
87+ 'discriminator_value_class_map' , 'model_fields' , 'model_computed_fields' ,
88+ 'model_extra' , 'model_fields_set' , 'oneof_schema_1_validator' ,
89+ 'oneof_schema_2_validator' , 'oneof_schema_3_validator'
90+ ):
91+ raise AttributeError (f"'{ type (self ).__name__ } ' object has no attribute '{ name } '" )
92+
93+ actual = object .__getattribute__ (self , 'actual_instance' )
94+ if actual is None :
95+ raise AttributeError (f"'{ type (self ).__name__ } ' object has no attribute '{ name } '" )
96+ return getattr (actual , name )
97+
98+ def __setattr__ (self , name : str , value ):
99+ """Delegate attribute setting to actual_instance for backward compatibility."""
100+ if name .startswith ('_' ) or name in (
101+ 'actual_instance' , 'one_of_schemas' , 'model_config' ,
102+ 'discriminator_value_class_map' , 'model_fields' , 'model_computed_fields' ,
103+ 'model_extra' , 'model_fields_set'
104+ ):
105+ super ().__setattr__ (name , value )
106+ return
107+
108+ try :
109+ actual = object .__getattribute__ (self , 'actual_instance' )
110+ if actual is not None :
111+ setattr (actual , name , value )
112+ return
113+ except AttributeError :
114+ pass
115+
116+ super ().__setattr__ (name , value )
117+
118+ def __getitem__ (self , key ):
119+ """Delegate subscript access to actual_instance for backward compatibility.
120+
121+ This allows code like `tx['height']` instead of `tx.actual_instance['height']`.
122+ """
123+ actual = object .__getattribute__ (self , 'actual_instance' )
124+ if actual is None :
125+ raise KeyError (key )
126+ # Try dict-style access first, then attribute access
127+ if hasattr (actual , '__getitem__' ):
128+ return actual [key ]
129+ return getattr (actual , key )
130+
131+
86132
87133 @classmethod
88134 def from_dict (cls , obj : Dict [str , Any ]) -> Self :
@@ -96,28 +142,21 @@ def from_json(cls, json_str: str) -> Self:
96142 return instance
97143
98144 error_messages = []
99- # Try to deserialize as int first (simpler case)
145+ # anyof_schema_1_validator: Optional[Entity] = None
100146 try :
101- # validation
102- parsed_data = json .loads (json_str )
103- if isinstance (parsed_data , int ):
104- instance .anyof_schema_2_validator = parsed_data
105- # assign value to actual_instance
106- instance .actual_instance = instance .anyof_schema_2_validator
107- return instance
147+ instance .actual_instance = Entity .from_json (json_str )
148+ return instance
108149 except (ValidationError , ValueError ) as e :
109- error_messages .append (str (e ))
110-
111- # If not an int, try to deserialize as Entity object (dict)
150+ error_messages .append (str (e ))
151+ # deserialize data into int
112152 try :
113- parsed_data = json .loads (json_str )
114- if isinstance (parsed_data , dict ):
115- # For Entity objects, just use the dict directly as actual_instance
116- # This avoids the infinite recursion
117- instance .actual_instance = parsed_data
118- return instance
153+ # validation
154+ instance .anyof_schema_2_validator = json .loads (json_str )
155+ # assign value to actual_instance
156+ instance .actual_instance = instance .anyof_schema_2_validator
157+ return instance
119158 except (ValidationError , ValueError ) as e :
120- error_messages .append (str (e ))
159+ error_messages .append (str (e ))
121160
122161 if error_messages :
123162 # no match
@@ -149,46 +188,6 @@ def to_str(self) -> str:
149188 """Returns the string representation of the actual instance"""
150189 return pprint .pformat (self .model_dump ())
151190
152- def __getitem__ (self , key ):
153- """Allow dict-style access to entity fields for backward compatibility."""
154- if self .actual_instance is None :
155- raise KeyError (key )
156-
157- if isinstance (self .actual_instance , dict ):
158- return self .actual_instance [key ]
159- elif isinstance (self .actual_instance , int ):
160- raise KeyError (f"Cannot access key '{ key } ' on int entity" )
161- else :
162- # For Entity instances
163- return getattr (self .actual_instance , key )
164-
165- def __getattr__ (self , name ):
166- """Allow attribute access to entity fields for backward compatibility."""
167- # Avoid infinite recursion for private attributes and Pydantic internals
168- if name .startswith ('_' ) or name in ('actual_instance' , 'anyof_schema_1_validator' , 'anyof_schema_2_validator' ):
169- raise AttributeError (f"'{ type (self ).__name__ } ' object has no attribute '{ name } '" )
170-
171- actual = object .__getattribute__ (self , 'actual_instance' )
172- if actual is None :
173- raise AttributeError (f"'{ type (self ).__name__ } ' object has no attribute '{ name } '" )
174-
175- if isinstance (actual , dict ):
176- if name in actual :
177- from graphsense .compat import DictModel
178- value = actual [name ]
179- # Wrap nested dicts in DictModel for attribute access
180- if isinstance (value , dict ):
181- return DictModel (value )
182- elif isinstance (value , list ):
183- return [DictModel (item ) if isinstance (item , dict ) else item for item in value ]
184- return value
185- raise AttributeError (f"'{ type (self ).__name__ } ' object has no attribute '{ name } '" )
186- elif isinstance (actual , int ):
187- raise AttributeError (f"Cannot access attribute '{ name } ' on int entity" )
188- else :
189- # For Entity instances
190- return getattr (actual , name )
191-
192191# TODO: Rewrite to not use raise_errors
193192Entity .model_rebuild (raise_errors = False )
194193
0 commit comments