@@ -234,6 +234,58 @@ def crons(self) -> List[str]:
234234 return self .cron
235235
236236
237+ class InstanceNameSelector (CoreModel ):
238+ name : Annotated [str , Field (description = "The fleet instance name" , min_length = 1 )]
239+
240+
241+ class InstanceHostnameSelector (CoreModel ):
242+ hostname : Annotated [
243+ str , Field (description = "The fleet instance hostname or IP address" , min_length = 1 )
244+ ]
245+
246+
247+ def _parse_fleet_instance_selector_fleet (v : Any ) -> Any :
248+ if isinstance (v , str ):
249+ return EntityReference .parse (v )
250+ return v
251+
252+
253+ class FleetInstanceSelectorConfig (CoreConfig ):
254+ @staticmethod
255+ def schema_extra (schema : Dict [str , Any ]):
256+ add_extra_schema_types (
257+ schema ["properties" ]["fleet" ],
258+ extra_types = [{"type" : "string" , "minLength" : 1 }],
259+ )
260+
261+
262+ class FleetInstanceSelector (generate_dual_core_model (FleetInstanceSelectorConfig )):
263+ fleet : Annotated [
264+ EntityReference ,
265+ Field (
266+ description = (
267+ "The fleet reference. For fleets owned by the current project, specify"
268+ " the fleet name. For a fleet from another project, specify"
269+ " `<project name>/<fleet name>` or an object with `project` and `name`."
270+ ),
271+ ),
272+ ]
273+ instance : Annotated [int , Field (description = "The fleet instance number" , ge = 0 )]
274+
275+ _validate_fleet = validator ("fleet" , pre = True , allow_reuse = True )(
276+ _parse_fleet_instance_selector_fleet
277+ )
278+
279+
280+ InstanceSelector = Union [InstanceNameSelector , InstanceHostnameSelector , FleetInstanceSelector ]
281+
282+
283+ def parse_instance_selector (v : Union [InstanceSelector , str ]) -> InstanceSelector :
284+ if isinstance (v , str ):
285+ return InstanceNameSelector (name = v )
286+ return v
287+
288+
237289class ProfileParamsConfig (CoreConfig ):
238290 @staticmethod
239291 def schema_extra (schema : Dict [str , Any ]):
@@ -249,6 +301,10 @@ def schema_extra(schema: Dict[str, Any]):
249301 schema ["properties" ]["idle_duration" ],
250302 extra_types = [{"type" : "string" }],
251303 )
304+ add_extra_schema_types (
305+ schema ["properties" ]["instances" ]["items" ],
306+ extra_types = [{"type" : "string" , "minLength" : 1 }],
307+ )
252308
253309
254310class ProfileParams (CoreModel ):
@@ -391,6 +447,18 @@ class ProfileParams(CoreModel):
391447 ),
392448 ),
393449 ] = None
450+ instances : Annotated [
451+ Optional [List [InstanceSelector ]],
452+ Field (
453+ description = (
454+ "The specific fleet instances to consider for reuse."
455+ " Each value can be an instance name string, or an object with"
456+ " `name`, `hostname`, or `fleet` and `instance`."
457+ " When set, the run is only placed on matching existing instances."
458+ ),
459+ min_items = 1 ,
460+ ),
461+ ] = None
394462 tags : Annotated [
395463 Optional [Dict [str , str ]],
396464 Field (
@@ -416,11 +484,23 @@ class ProfileParams(CoreModel):
416484 parse_idle_duration
417485 )
418486 _validate_fleets = validator ("fleets" , allow_reuse = True , each_item = True )(EntityReference .parse )
487+ _validate_instances = validator ("instances" , pre = True , allow_reuse = True , each_item = True )(
488+ parse_instance_selector
489+ )
419490 _validate_tags = validator ("tags" , pre = True , allow_reuse = True )(tags_validator )
420491 _validate_backend_options = validator ("backend_options" , allow_reuse = True )(
421492 validate_backend_options
422493 )
423494
495+ def dict (self , * args , ** kwargs ) -> Dict :
496+ # super() does not work with pydantic-duality
497+ res = CoreModel .dict (self , * args , ** kwargs )
498+ # Older servers reject unknown fields in profile/request models. Since `instances`
499+ # did not exist before, do not serialize it when unset.
500+ if self .instances is None :
501+ res .pop ("instances" , None )
502+ return res
503+
424504
425505class ProfileProps (CoreModel ):
426506 name : Annotated [
0 commit comments