44from typing import Any
55
66from models .errors import SchemaError
7+ from models .from_dict_validation import (
8+ require_dict ,
9+ require_key ,
10+ require_non_empty_str ,
11+ require_non_empty_str_field ,
12+ require_type ,
13+ )
714
815
916@dataclass (frozen = True )
@@ -20,22 +27,10 @@ class Composer:
2027
2128 @classmethod
2229 def from_dict (cls , raw : dict [str , Any ], * , composer_id : str ) -> "Composer" :
23- if not isinstance (raw , dict ):
24- raise SchemaError (
25- "Composer" ,
26- "composerData" ,
27- hint = f"expected object, got { type (raw ).__name__ } " ,
28- )
29- if not isinstance (composer_id , str ) or not composer_id :
30- raise SchemaError (
31- "Composer" ,
32- "composerId" ,
33- hint = f"expected non-empty str, got { type (composer_id ).__name__ } " ,
34- )
35- if "fullConversationHeadersOnly" not in raw :
36- raise SchemaError ("Composer" , "fullConversationHeadersOnly" )
37- if "createdAt" not in raw :
38- raise SchemaError ("Composer" , "createdAt" )
30+ raw = require_dict (raw , model = "Composer" , field = "composerData" )
31+ require_non_empty_str (composer_id , model = "Composer" , field = "composerId" )
32+ require_key (raw , "fullConversationHeadersOnly" , model = "Composer" )
33+ require_key (raw , "createdAt" , model = "Composer" )
3934
4035 created_at = raw .get ("createdAt" )
4136 # Numeric-only on purpose: a 2026-05 scan of 17/17 live composers on
@@ -49,13 +44,14 @@ def from_dict(cls, raw: dict[str, Any], *, composer_id: str) -> "Composer":
4944 hint = f"expected timestamp number, got { type (created_at ).__name__ } " ,
5045 )
5146
52- headers = raw .get ("fullConversationHeadersOnly" )
53- if not isinstance (headers , list ):
54- raise SchemaError (
55- "Composer" ,
56- "fullConversationHeadersOnly" ,
57- hint = f"expected list, got { type (headers ).__name__ } " ,
58- )
47+ headers_value = raw .get ("fullConversationHeadersOnly" )
48+ headers = require_type (
49+ headers_value ,
50+ list ,
51+ model = "Composer" ,
52+ field = "fullConversationHeadersOnly" ,
53+ hint = f"expected list, got { type (headers_value ).__name__ } " ,
54+ )
5955
6056 model_config = raw .get ("modelConfig" ) or {}
6157 if not isinstance (model_config , dict ):
@@ -82,19 +78,10 @@ class WorkspaceLocalComposer:
8278
8379 @classmethod
8480 def from_dict (cls , raw : dict [str , Any ]) -> "WorkspaceLocalComposer" :
85- if not isinstance (raw , dict ):
86- raise SchemaError (
87- "WorkspaceLocalComposer" ,
88- "composer" ,
89- hint = f"expected object, got { type (raw ).__name__ } " ,
90- )
91- composer_id = raw .get ("composerId" )
92- if not isinstance (composer_id , str ) or not composer_id :
93- raise SchemaError (
94- "WorkspaceLocalComposer" ,
95- "composerId" ,
96- hint = f"expected non-empty str, got { type (composer_id ).__name__ } " ,
97- )
81+ raw = require_dict (raw , model = "WorkspaceLocalComposer" , field = "composer" )
82+ composer_id = require_non_empty_str_field (
83+ raw , "composerId" , model = "WorkspaceLocalComposer"
84+ )
9885 return cls (
9986 composer_id = composer_id ,
10087 last_updated_at = raw .get ("lastUpdatedAt" ),
@@ -111,16 +98,6 @@ class Bubble:
11198
11299 @classmethod
113100 def from_dict (cls , raw : dict [str , Any ], * , bubble_id : str ) -> "Bubble" :
114- if not isinstance (raw , dict ):
115- raise SchemaError (
116- "Bubble" ,
117- "bubble" ,
118- hint = f"expected object, got { type (raw ).__name__ } " ,
119- )
120- if not isinstance (bubble_id , str ) or not bubble_id :
121- raise SchemaError (
122- "Bubble" ,
123- "bubbleId" ,
124- hint = f"expected non-empty str, got { type (bubble_id ).__name__ } " ,
125- )
101+ raw = require_dict (raw , model = "Bubble" , field = "bubble" )
102+ require_non_empty_str (bubble_id , model = "Bubble" , field = "bubbleId" )
126103 return cls (bubble_id = bubble_id , raw = raw )
0 commit comments