@@ -152,6 +152,17 @@ def id(self) -> str:
152152 """Returns private_attribute_id of the entity."""
153153 return self .private_attribute_id
154154
155+ def _manual_assignment_validation (self , _ : ParamsValidationInfo ) -> EntityBase :
156+ """
157+ Pre-expansion contextual validation for the entity.
158+ This handles validation for the entity manually assigned.
159+ """
160+ return self
161+
162+ def _per_entity_type_validation (self , _ : ParamsValidationInfo ) -> EntityBase :
163+ """Contextual validation with validation logic bond with the specific entity type."""
164+ return self
165+
155166
156167class _CombinedMeta (type (Flow360BaseModel ), type ):
157168 pass
@@ -265,18 +276,42 @@ def _ensure_entities_after_expansion(self, param_info: ParamsValidationInfo):
265276 With delayed selector expansion, stored_entities may be empty if only selectors
266277 are defined.
267278 """
268- # If stored_entities already has entities, validation passes
269- if self .stored_entities :
270- return self
279+ is_empty = True
280+ # If stored_entities already has entities (user manual assignment), validation passes
281+ manual_assignments : List [EntityBase ] = self .stored_entities
282+ # pylint: disable=protected-access
283+ if manual_assignments :
284+ filtered_assignments = [
285+ item
286+ for item in manual_assignments
287+ if item ._manual_assignment_validation (param_info ) is not None
288+ ]
289+ # Use object.__setattr__ to bypass validate_on_assignment and avoid recursion
290+ object .__setattr__ (
291+ self ,
292+ "stored_entities" ,
293+ filtered_assignments ,
294+ )
295+
296+ for item in filtered_assignments :
297+ item ._per_entity_type_validation (param_info )
298+
299+ if filtered_assignments :
300+ is_empty = False
271301
272302 # No stored_entities - check if selectors will produce any entities
273303 if self .selectors :
274- expanded = param_info .expand_entity_list (self )
304+ expanded : List [ EntityBase ] = param_info .expand_entity_list (self )
275305 if expanded :
306+ for item in expanded :
307+ item ._per_entity_type_validation (param_info )
308+ # Known non-empty
276309 return self
277310
278311 # Neither stored_entities nor selectors produced any entities
279- raise ValueError ("No entities were selected." )
312+ if is_empty :
313+ raise ValueError ("No entities were selected." )
314+ return self
280315
281316 @classmethod
282317 def _get_valid_entity_types (cls ):
@@ -311,17 +346,17 @@ def _get_valid_entity_types(cls):
311346 raise TypeError ("Cannot extract valid entity types." )
312347
313348 @classmethod
314- def _process_selector (cls , selector : EntitySelector , valid_type_names : List [str ]) -> dict :
349+ def _validate_selector (cls , selector : EntitySelector , valid_type_names : List [str ]) -> dict :
315350 """Process and validate an EntitySelector object."""
316351 if selector .target_class not in valid_type_names :
317352 raise ValueError (
318353 f"Selector target_class ({ selector .target_class } ) is incompatible "
319354 f"with EntityList types { valid_type_names } ."
320355 )
321- return selector . model_dump ()
356+ return selector
322357
323358 @classmethod
324- def _process_entity (cls , entity : Union [EntityBase , Any ]) -> EntityBase :
359+ def _validate_entity (cls , entity : Union [EntityBase , Any ]) -> EntityBase :
325360 """Process and validate an entity object."""
326361 if isinstance (entity , EntityBase ):
327362 return entity
@@ -333,12 +368,12 @@ def _process_entity(cls, entity: Union[EntityBase, Any]) -> EntityBase:
333368
334369 @classmethod
335370 def _build_result (
336- cls , entities_to_store : List [EntityBase ], entity_patterns_to_store : List [dict ]
371+ cls , entities_to_store : List [EntityBase ], entity_selectors_to_store : List [dict ]
337372 ) -> dict :
338373 """Build the final result dictionary."""
339374 return {
340375 "stored_entities" : entities_to_store ,
341- "selectors" : entity_patterns_to_store if entity_patterns_to_store else None ,
376+ "selectors" : entity_selectors_to_store if entity_selectors_to_store else None ,
342377 }
343378
344379 @classmethod
@@ -348,13 +383,13 @@ def _process_single_item(
348383 item : Union [EntityBase , EntitySelector ],
349384 valid_type_names : List [str ],
350385 entities_to_store : List [EntityBase ],
351- entity_patterns_to_store : List [dict ],
386+ entity_selectors_to_store : List [dict ],
352387 ) -> None :
353388 """Process a single item (entity or selector) and add to appropriate storage lists."""
354389 if isinstance (item , EntitySelector ):
355- entity_patterns_to_store .append (cls ._process_selector (item , valid_type_names ))
390+ entity_selectors_to_store .append (cls ._validate_selector (item , valid_type_names ))
356391 else :
357- processed_entity = cls ._process_entity (item )
392+ processed_entity = cls ._validate_entity (item )
358393 entities_to_store .append (processed_entity )
359394
360395 @pd .model_validator (mode = "before" )
@@ -367,7 +402,7 @@ def deserializer(cls, input_data: Union[dict, list, EntityBase, EntitySelector])
367402 field validator, which runs after deserialization but before discriminator validation.
368403 """
369404 entities_to_store = []
370- entity_patterns_to_store = []
405+ entity_selectors_to_store = []
371406 valid_types = tuple (cls ._get_valid_entity_types ())
372407 valid_type_names = [t .__name__ for t in valid_types ]
373408
@@ -379,15 +414,15 @@ def deserializer(cls, input_data: Union[dict, list, EntityBase, EntitySelector])
379414 for item in input_data :
380415 if isinstance (item , list ): # Nested list comes from assets __getitem__
381416 # Process all entities without filtering
382- processed_entities = [cls ._process_entity (individual ) for individual in item ]
417+ processed_entities = [cls ._validate_entity (individual ) for individual in item ]
383418 entities_to_store .extend (processed_entities )
384419 else :
385420 # Single entity or selector
386421 cls ._process_single_item (
387422 item ,
388423 valid_type_names ,
389424 entities_to_store ,
390- entity_patterns_to_store ,
425+ entity_selectors_to_store ,
391426 )
392427 elif isinstance (input_data , dict ): # Deserialization
393428 # With delayed selector expansion, stored_entities may be absent if only selectors are defined.
@@ -403,13 +438,13 @@ def deserializer(cls, input_data: Union[dict, list, EntityBase, EntitySelector])
403438 input_data ,
404439 valid_type_names ,
405440 entities_to_store ,
406- entity_patterns_to_store ,
441+ entity_selectors_to_store ,
407442 )
408443
409- if not entities_to_store and not entity_patterns_to_store :
444+ if not entities_to_store and not entity_selectors_to_store :
410445 raise ValueError (
411446 f"Can not find any valid entity of type { [valid_type .__name__ for valid_type in valid_types ]} "
412447 f" from the input."
413448 )
414449
415- return cls ._build_result (entities_to_store , entity_patterns_to_store )
450+ return cls ._build_result (entities_to_store , entity_selectors_to_store )
0 commit comments