1313from enum import Enum
1414from typing import Any
1515
16+ from pydantic import BaseModel , ConfigDict , Field
17+
1618
1719class NotificationTriggerType (Enum ):
1820 """Represents the different TFE notifications that can be sent as a run's progress transitions between different states."""
@@ -187,69 +189,57 @@ def __repr__(self) -> str:
187189 return f"NotificationConfiguration(id='{ self .id } ', name='{ self .name } ', enabled={ self .enabled } )"
188190
189191
190- class NotificationConfigurationListOptions :
192+ def _serialize_triggers (
193+ triggers : list [NotificationTriggerType | str ],
194+ ) -> list [str ]:
195+ """Serialize trigger enums or raw strings to their wire value."""
196+ return [t .value if isinstance (t , NotificationTriggerType ) else t for t in triggers ]
197+
198+
199+ def _validate_triggers (
200+ triggers : list [NotificationTriggerType | str ],
201+ ) -> list [str ]:
202+ """Collect errors for any non-enum, non-known-string trigger entries."""
203+ errors : list [str ] = []
204+ for trigger in triggers :
205+ if isinstance (trigger , NotificationTriggerType ):
206+ continue
207+ try :
208+ NotificationTriggerType (trigger )
209+ except ValueError :
210+ errors .append (f"Invalid trigger type: { trigger } " )
211+ return errors
212+
213+
214+ class NotificationConfigurationListOptions (BaseModel ):
191215 """Represents the options for listing notification configurations."""
192216
193- # Type annotations for instance attributes
194- page_size : int | None
195- subscribable_choice : NotificationConfigurationSubscribableChoice | None
217+ model_config = ConfigDict (populate_by_name = True , arbitrary_types_allowed = True )
196218
197- def __init__ (
198- self ,
199- page_size : int | None = None ,
200- subscribable_choice : NotificationConfigurationSubscribableChoice | None = None ,
201- ):
202- self .page_size = page_size
203- self .subscribable_choice = subscribable_choice
219+ page_size : int | None = Field (default = None , alias = "page[size]" )
220+ subscribable_choice : NotificationConfigurationSubscribableChoice | None = Field (
221+ default = None , exclude = True
222+ )
204223
205224 def to_dict (self ) -> dict [str , Any ]:
206225 """Convert to dictionary for API requests."""
207- params = {}
208-
209- if self .page_size is not None :
210- params ["page[size]" ] = self .page_size
211-
212- return params
226+ return self .model_dump (by_alias = True , exclude_none = True )
213227
214228
215- class NotificationConfigurationCreateOptions :
229+ class NotificationConfigurationCreateOptions ( BaseModel ) :
216230 """Represents the options for creating a new notification configuration."""
217231
218- # Type annotations for instance attributes
232+ model_config = ConfigDict (populate_by_name = True , arbitrary_types_allowed = True )
233+
219234 destination_type : NotificationDestinationType
220235 enabled : bool
221236 name : str
222- token : str | None
223- triggers : list [NotificationTriggerType ]
224- url : str | None
225- email_addresses : list [str ]
226- email_users : list [Any ]
227- subscribable_choice : NotificationConfigurationSubscribableChoice | None
228-
229- def __init__ (
230- self ,
231- destination_type : NotificationDestinationType ,
232- enabled : bool ,
233- name : str ,
234- token : str | None = None ,
235- triggers : list [NotificationTriggerType ] | None = None ,
236- url : str | None = None ,
237- email_addresses : list [str ] | None = None ,
238- email_users : list [Any ] | None = None ,
239- subscribable_choice : NotificationConfigurationSubscribableChoice | None = None ,
240- ):
241- # Required fields
242- self .destination_type = destination_type
243- self .enabled = enabled
244- self .name = name
245-
246- # Optional fields
247- self .token = token
248- self .triggers = triggers or []
249- self .url = url
250- self .email_addresses = email_addresses or []
251- self .email_users = email_users or []
252- self .subscribable_choice = subscribable_choice
237+ token : str | None = None
238+ triggers : list [NotificationTriggerType | str ] = Field (default_factory = list )
239+ url : str | None = None
240+ email_addresses : list [str ] = Field (default_factory = list )
241+ email_users : list [Any ] = Field (default_factory = list )
242+ subscribable_choice : NotificationConfigurationSubscribableChoice | None = None
253243
254244 def to_dict (self ) -> dict [str , Any ]:
255245 """Convert to dictionary for API requests."""
@@ -262,99 +252,70 @@ def to_dict(self) -> dict[str, Any]:
262252 },
263253 }
264254
265- # Add optional attributes
266255 if self .token is not None :
267256 data ["attributes" ]["token" ] = self .token
268257
269258 if self .triggers :
270- data ["attributes" ]["triggers" ] = [
271- trigger .value for trigger in self .triggers
272- ]
259+ data ["attributes" ]["triggers" ] = _serialize_triggers (self .triggers )
273260
274261 if self .url is not None :
275262 data ["attributes" ]["url" ] = self .url
276263
277264 if self .email_addresses :
278265 data ["attributes" ]["email-addresses" ] = self .email_addresses
279266
280- # Handle relationships
281267 if self .email_users :
282- data ["relationships" ] = data .get ("relationships" , {})
283- data ["relationships" ]["users" ] = {
284- "data" : [
285- {
286- "type" : "users" ,
287- "id" : user .id if hasattr (user , "id" ) else str (user ),
288- }
289- for user in self .email_users
290- ]
268+ data ["relationships" ] = {
269+ "users" : {
270+ "data" : [
271+ {
272+ "type" : "users" ,
273+ "id" : user .id if hasattr (user , "id" ) else str (user ),
274+ }
275+ for user in self .email_users
276+ ]
277+ }
291278 }
292279
293280 return data
294281
295- def validate (self ) -> list [str ]:
282+ def validate (self ) -> list [str ]: # type: ignore[override]
296283 """Validate the create options and return any errors."""
297- errors = []
284+ errors : list [ str ] = []
298285
299- # Required field validation
300286 if not self .name or not self .name .strip ():
301287 errors .append ("Name is required" )
302288
303- if not isinstance (self .enabled , bool ):
304- errors .append ("Enabled must be a boolean" ) # type: ignore[unreachable]
305-
306- # URL validation for certain destination types
307- if self .destination_type in [
289+ if self .destination_type in (
308290 NotificationDestinationType .GENERIC ,
309291 NotificationDestinationType .SLACK ,
310292 NotificationDestinationType .MICROSOFT_TEAMS ,
311- ] :
293+ ) :
312294 if not self .url :
313295 errors .append ("URL is required for this destination type" )
314296
315- # Trigger validation
316- for trigger in self .triggers :
317- if not isinstance (trigger , NotificationTriggerType ):
318- errors .append (f"Invalid trigger type: { trigger } " ) # type: ignore[unreachable]
297+ errors .extend (_validate_triggers (self .triggers ))
319298
320299 return errors
321300
322301
323- class NotificationConfigurationUpdateOptions :
302+ class NotificationConfigurationUpdateOptions ( BaseModel ) :
324303 """Represents the options for updating an existing notification configuration."""
325304
326- # Type annotations for instance attributes
327- enabled : bool | None
328- name : str | None
329- token : str | None
330- triggers : list [NotificationTriggerType ] | None
331- url : str | None
332- email_addresses : list [str ] | None
333- email_users : list [Any ] | None
334-
335- def __init__ (
336- self ,
337- enabled : bool | None = None ,
338- name : str | None = None ,
339- token : str | None = None ,
340- triggers : list [NotificationTriggerType ] | None = None ,
341- url : str | None = None ,
342- email_addresses : list [str ] | None = None ,
343- email_users : list [Any ] | None = None ,
344- ):
345- self .enabled = enabled
346- self .name = name
347- self .token = token
348- self .triggers = triggers
349- self .url = url
350- self .email_addresses = email_addresses
351- self .email_users = email_users
305+ model_config = ConfigDict (populate_by_name = True , arbitrary_types_allowed = True )
306+
307+ enabled : bool | None = None
308+ name : str | None = None
309+ token : str | None = None
310+ triggers : list [NotificationTriggerType | str ] | None = None
311+ url : str | None = None
312+ email_addresses : list [str ] | None = None
313+ email_users : list [Any ] | None = None
352314
353315 def to_dict (self ) -> dict [str , Any ]:
354316 """Convert to dictionary for API requests."""
355317 data : dict [str , Any ] = {"type" : "notification-configurations" , "attributes" : {}}
356318
357- # Add only specified attributes
358319 if self .enabled is not None :
359320 data ["attributes" ]["enabled" ] = self .enabled
360321
@@ -365,44 +326,38 @@ def to_dict(self) -> dict[str, Any]:
365326 data ["attributes" ]["token" ] = self .token
366327
367328 if self .triggers is not None :
368- data ["attributes" ]["triggers" ] = [
369- trigger .value for trigger in self .triggers
370- ]
329+ data ["attributes" ]["triggers" ] = _serialize_triggers (self .triggers )
371330
372331 if self .url is not None :
373332 data ["attributes" ]["url" ] = self .url
374333
375334 if self .email_addresses is not None :
376335 data ["attributes" ]["email-addresses" ] = self .email_addresses
377336
378- # Handle relationships
379337 if self .email_users is not None :
380- data ["relationships" ] = data .get ("relationships" , {})
381- data ["relationships" ]["users" ] = {
382- "data" : [
383- {
384- "type" : "users" ,
385- "id" : user .id if hasattr (user , "id" ) else str (user ),
386- }
387- for user in self .email_users
388- ]
338+ data ["relationships" ] = {
339+ "users" : {
340+ "data" : [
341+ {
342+ "type" : "users" ,
343+ "id" : user .id if hasattr (user , "id" ) else str (user ),
344+ }
345+ for user in self .email_users
346+ ]
347+ }
389348 }
390349
391350 return data
392351
393- def validate (self ) -> list [str ]:
352+ def validate (self ) -> list [str ]: # type: ignore[override]
394353 """Validate the update options and return any errors."""
395- errors = []
354+ errors : list [ str ] = []
396355
397- # Name validation (if provided)
398356 if self .name is not None and (not self .name or not self .name .strip ()):
399357 errors .append ("Name cannot be empty" )
400358
401- # Trigger validation (if provided)
402359 if self .triggers is not None :
403- for trigger in self .triggers :
404- if not isinstance (trigger , NotificationTriggerType ):
405- errors .append (f"Invalid trigger type: { trigger } " ) # type: ignore[unreachable]
360+ errors .extend (_validate_triggers (self .triggers ))
406361
407362 return errors
408363
0 commit comments