66class ArcsecondTargetListsResource (ArcsecondAPIEndpoint ):
77 """Target-list specific helpers built on top of the generic endpoint contract."""
88
9- target_relation_keys = ("targets" , "target_uuids" , "target_ids" )
9+ target_relation_key = "targets"
10+ target_writable_fields = (
11+ "id" ,
12+ "pk" ,
13+ "object" ,
14+ "name" ,
15+ "identifier" ,
16+ "target_class" ,
17+ "mode" ,
18+ "color" ,
19+ "notes" ,
20+ "tags" ,
21+ "profile" ,
22+ "organisation" ,
23+ )
1024
1125 def _ensure_iterable (self , values ):
1226 if values is None :
1327 return None
28+ if isinstance (values , dict ):
29+ return [values ]
1430 if isinstance (values , (str , int )):
1531 return [values ]
1632 return list (values )
1733
18- def _normalise_target_references (self , targets ):
34+ def _target_payload_identity (self , target ):
35+ if target .get ("id" ) is not None :
36+ return ("id" , target ["id" ])
37+ if target .get ("pk" ) is not None :
38+ return ("pk" , target ["pk" ])
39+ return (
40+ "composite" ,
41+ target .get ("target_class" ),
42+ target .get ("identifier" ),
43+ target .get ("name" ),
44+ target .get ("mode" ),
45+ )
46+
47+ def _normalise_target_payloads (self , targets ):
1948 values = self ._ensure_iterable (targets )
2049 if values is None :
2150 return None
2251
23- refs = []
52+ payloads = []
2453 for target in values :
25- if isinstance (target , dict ):
26- ref = (
27- target .get ("uuid" )
28- or target .get ("id" )
29- or target .get ("pk" )
30- or target .get ("name" )
54+ if not isinstance (target , dict ):
55+ raise ArcsecondError (
56+ "Target list helpers expect target payload dictionaries, not scalar IDs or UUIDs. "
57+ "Pass dictionaries such as `plan_target_payload(...).payload` or target objects returned "
58+ "by `api.targets.read()/list()/upsert()`."
3159 )
32- if ref is None :
33- raise ArcsecondError (
34- "Target dictionaries must include one of: uuid, id, pk or name."
35- )
36- refs .append (ref )
37- else :
38- refs .append (target )
39- return refs
40-
41- def _target_key_from_payload (self , payload , target_key = None ):
42- if target_key :
43- return target_key
44- for key in self .target_relation_keys :
45- if payload and key in payload :
46- return key
47- return self .target_relation_keys [0 ]
60+
61+ payload = {
62+ key : value
63+ for key , value in target .items ()
64+ if key in self .target_writable_fields and value is not None
65+ }
66+ if not payload :
67+ raise ArcsecondError (
68+ "Target dictionaries must include at least one writable target field."
69+ )
70+
71+ payloads .append (payload )
72+ return payloads
4873
4974 def _build_payload (self , json = None , targets = None , target_key = None , ** fields ):
5075 payload = super ()._build_payload (json = json , ** fields ) or {}
51- normalised_targets = self ._normalise_target_references (targets )
76+ normalised_targets = self ._normalise_target_payloads (targets )
5277 if normalised_targets is not None :
53- payload [self ._target_key_from_payload (payload , target_key = target_key )] = (
54- normalised_targets
55- )
78+ payload [target_key or self .target_relation_key ] = normalised_targets
5679 return payload or None
5780
5881 def create (self , json = None , targets = None , target_key = None , ** fields ):
@@ -74,14 +97,14 @@ def upsert(self, match_field="name", json=None, targets=None, target_key=None, *
7497 return super ().upsert (match_field = match_field , json = payload )
7598
7699 def _read_target_refs (self , target_list , target_key = None ):
77- key = self . _target_key_from_payload ( target_list or {}, target_key = target_key )
100+ key = target_key or self . target_relation_key
78101 raw_targets = (target_list or {}).get (key , [])
79- refs = self ._normalise_target_references (raw_targets ) or []
102+ refs = self ._normalise_target_payloads (raw_targets ) or []
80103 return key , refs
81104
82105 def set_targets (self , id_name_uuid , targets , target_key = None ):
83- target_key = self ._target_key_from_payload ({}, target_key = target_key )
84- return self .update (id_name_uuid , ** {target_key : self ._normalise_target_references (targets )})
106+ target_key = target_key or self .target_relation_key
107+ return self .update (id_name_uuid , ** {target_key : self ._normalise_target_payloads (targets )})
85108
86109 def clear_targets (self , id_name_uuid , target_key = None ):
87110 return self .set_targets (id_name_uuid , [], target_key = target_key )
@@ -92,9 +115,14 @@ def add_targets(self, id_name_uuid, targets, target_key=None):
92115 return None , error
93116
94117 key , current_refs = self ._read_target_refs (target_list , target_key = target_key )
95- for ref in self ._normalise_target_references (targets ) or []:
96- if ref not in current_refs :
97- current_refs .append (ref )
118+ current_identities = {
119+ self ._target_payload_identity (target ): target for target in current_refs
120+ }
121+ for target in self ._normalise_target_payloads (targets ) or []:
122+ identity = self ._target_payload_identity (target )
123+ if identity not in current_identities :
124+ current_refs .append (target )
125+ current_identities [identity ] = target
98126 return self .update (id_name_uuid , ** {key : current_refs })
99127
100128 def remove_targets (self , id_name_uuid , targets , target_key = None ):
@@ -103,8 +131,15 @@ def remove_targets(self, id_name_uuid, targets, target_key=None):
103131 return None , error
104132
105133 key , current_refs = self ._read_target_refs (target_list , target_key = target_key )
106- refs_to_remove = set (self ._normalise_target_references (targets ) or [])
107- remaining_refs = [ref for ref in current_refs if ref not in refs_to_remove ]
134+ refs_to_remove = {
135+ self ._target_payload_identity (target )
136+ for target in (self ._normalise_target_payloads (targets ) or [])
137+ }
138+ remaining_refs = [
139+ ref
140+ for ref in current_refs
141+ if self ._target_payload_identity (ref ) not in refs_to_remove
142+ ]
108143 return self .update (id_name_uuid , ** {key : remaining_refs })
109144
110145 def add_target (self , id_name_uuid , target , target_key = None ):
0 commit comments