22
33import logging
44from datetime import datetime
5- from typing import Any , Dict , List , Optional , Type
5+ from typing import Any , Dict , List , Optional , Type , Union
66
77from pydantic import BaseModel , Field , create_model
88
@@ -28,52 +28,49 @@ def create_model_from_schema(
2828
2929 Raises:
3030 ValueError: If schema is invalid or unsupported
31-
32- Example:
33- schema = {
34- "type": "object",
35- "properties": {
36- "name": {"type": "string", "description": "User name"},
37- "age": {"type": "integer", "minimum": 0}
38- },
39- "required": ["name"]
40- }
41- UserModel = PydanticModelFactory.create_model_from_schema("User", schema)
4231 """
43- if not PydanticModelFactory . validate_schema (schema ):
44- raise ValueError (f"Invalid schema for model ' { model_name } ' " )
32+ if not isinstance (schema , dict ):
33+ raise ValueError (f"Schema must be a dictionary, got { type ( schema ) } " )
4534
4635 if schema .get ("type" ) != "object" :
47- raise ValueError ("Schema must be of type 'object'" )
36+ raise ValueError (f "Schema must be of type 'object', got { schema . get ( 'type' ) } " )
4837
4938 properties = schema .get ("properties" , {})
5039 required_fields = set (schema .get ("required" , []))
5140
52- # Build field definitions
53- field_definitions = {}
41+ if not properties :
42+ logger .warning ("Schema '%s' has no properties defined" , model_name )
43+
44+ # Build field definitions for create_model
45+ field_definitions : Dict [str , Any ] = {}
5446
5547 for field_name , field_schema in properties .items ():
5648 try :
49+ is_required = field_name in required_fields
5750 field_type , field_info = PydanticModelFactory ._process_field_schema (
58- field_name , field_schema , field_name in required_fields
51+ field_name , field_schema , is_required
5952 )
6053 field_definitions [field_name ] = (field_type , field_info )
6154 except Exception as e :
6255 logger .warning ("Error processing field '%s' in schema '%s': %s" , field_name , model_name , e )
6356 # Use Any type as fallback
57+ fallback_type = Optional [Any ] if field_name not in required_fields else Any
6458 field_definitions [field_name ] = (
65- Optional [ Any ] if field_name not in required_fields else Any ,
59+ fallback_type ,
6660 Field (description = f"Field processing failed: { e } " ),
6761 )
6862
6963 # Create the model
7064 try :
71- return create_model (model_name , __base__ = base_class , ** field_definitions )
65+ model_class = create_model (model_name , __base__ = base_class , ** field_definitions )
66+ return model_class
7267 except Exception as e :
7368 raise ValueError (f"Failed to create model '{ model_name } ': { e } " ) from e
7469
7570 @staticmethod
76- def _process_field_schema (field_name : str , field_schema : Dict [str , Any ], is_required : bool ) -> tuple [Type , Any ]:
71+ def _process_field_schema (
72+ field_name : str , field_schema : Dict [str , Any ], is_required : bool
73+ ) -> tuple [Type [Any ], Any ]:
7774 """Process a single field schema into Pydantic field type and info.
7875
7976 Args:
@@ -88,7 +85,7 @@ def _process_field_schema(field_name: str, field_schema: Dict[str, Any], is_requ
8885
8986 # Handle optional fields
9087 if not is_required :
91- field_type = Optional [field_type ]
88+ field_type = Optional [field_type ] # type: ignore[assignment]
9289
9390 # Create Field with metadata
9491 field_kwargs = {}
@@ -120,145 +117,98 @@ def _process_field_schema(field_name: str, field_schema: Dict[str, Any], is_requ
120117 try :
121118 from pydantic import EmailStr
122119
123- field_type = EmailStr if is_required else Optional [EmailStr ]
120+ field_type = EmailStr if is_required else Optional [EmailStr ] # type: ignore[assignment]
124121 except ImportError :
125- logger .warning ("EmailStr not available, using str for email format" )
122+ logger .warning ("EmailStr not available, using str for email field '%s'" , field_name )
123+ field_type = str if is_required else Optional [str ] # type: ignore[assignment]
126124 elif format_type == "uri" :
127125 try :
128126 from pydantic import HttpUrl
129127
130- field_type = HttpUrl if is_required else Optional [HttpUrl ]
128+ field_type = HttpUrl if is_required else Optional [HttpUrl ] # type: ignore[assignment]
131129 except ImportError :
132- logger .warning ("HttpUrl not available, using str for uri format" )
130+ logger .warning ("HttpUrl not available, using str for uri field '%s'" , field_name )
131+ field_type = str if is_required else Optional [str ] # type: ignore[assignment]
133132 elif format_type == "date-time" :
134- field_type = datetime if is_required else Optional [datetime ]
133+ field_type = datetime if is_required else Optional [datetime ] # type: ignore[assignment]
135134
136- # Handle array constraints
137- if field_schema .get ("type" ) == "array" :
138- if "minItems" in field_schema :
139- field_kwargs ["min_length" ] = field_schema ["minItems" ]
140- if "maxItems" in field_schema :
141- field_kwargs ["max_length" ] = field_schema ["maxItems" ]
135+ field_info = Field (** field_kwargs ) if field_kwargs else Field ()
142136
143- if field_kwargs :
144- return field_type , Field (** field_kwargs )
145- else :
146- return field_type , Field () if is_required else Field (default = None )
137+ return field_type , field_info
147138
148139 @staticmethod
149- def _get_python_type (field_schema : Dict [str , Any ]) -> Type :
140+ def _get_python_type (schema : Dict [str , Any ]) -> Type [ Any ] :
150141 """Convert JSON schema type to Python type.
151142
152143 Args:
153- field_schema : JSON schema for the field
144+ schema : JSON schema dictionary
154145
155146 Returns:
156147 Python type corresponding to the schema
157148 """
158- schema_type = field_schema .get ("type" )
149+ schema_type = schema .get ("type" )
159150
160151 if schema_type == "string" :
161- if "enum" in field_schema :
162- # Use Literal type instead of Enum for better string handling
163- from typing import Literal
164-
165- enum_values = field_schema ["enum" ]
166- if len (enum_values ) == 1 :
167- return Literal [enum_values [0 ]]
168- else :
169- return Literal [tuple (enum_values )]
170152 return str
171-
172153 elif schema_type == "integer" :
173154 return int
174-
175155 elif schema_type == "number" :
176156 return float
177-
178157 elif schema_type == "boolean" :
179158 return bool
180-
181159 elif schema_type == "array" :
182- items_schema = field_schema .get ("items" , {})
160+ items_schema = schema .get ("items" , {})
183161 if items_schema :
184162 item_type = PydanticModelFactory ._get_python_type (items_schema )
185- return List [item_type ]
186- return List [ Any ]
187-
163+ return List [item_type ] # type: ignore[valid-type]
164+ else :
165+ return List [ Any ]
188166 elif schema_type == "object" :
189- # Handle nested objects
190- if "properties" in field_schema :
191- nested_model_name = f"NestedModel_{ abs (hash (str (field_schema )))} "
192- return PydanticModelFactory .create_model_from_schema (nested_model_name , field_schema )
167+ # For nested objects, we could recursively create models
168+ # For now, return Dict[str, Any]
193169 return Dict [str , Any ]
194-
170+ elif schema_type is None and "anyOf" in schema :
171+ # Handle anyOf by creating Union types
172+ types = []
173+ for sub_schema in schema ["anyOf" ]:
174+ sub_type = PydanticModelFactory ._get_python_type (sub_schema )
175+ types .append (sub_type )
176+ if len (types ) == 1 :
177+ return types [0 ]
178+ elif len (types ) == 2 and type (None ) in types :
179+ # This is Optional[T]
180+ non_none_type = next (t for t in types if t is not type (None ))
181+ return Optional [non_none_type ] # type: ignore[return-value]
182+ else :
183+ return Union [tuple (types )] # type: ignore[return-value]
195184 else :
196- # Default to Any for unknown types
197185 logger .warning ("Unknown schema type '%s', using Any" , schema_type )
198186 return Any
199187
200188 @staticmethod
201- def validate_schema ( schema : Dict [ str , Any ]) -> bool :
202- """Validate that the schema is a valid JSON schema for object creation .
189+ def get_schema_info ( model_class : Type [ BaseModel ]) -> Dict [ str , Any ] :
190+ """Get schema information from a Pydantic model .
203191
204192 Args:
205- schema: JSON schema dictionary
193+ model_class: Pydantic model class
206194
207195 Returns:
208- True if schema is valid, False otherwise
196+ Dictionary containing schema information
209197 """
210198 try :
211- # Basic validation
212- if not isinstance (schema , dict ):
213- return False
214-
215- if schema .get ("type" ) != "object" :
216- return False
217-
218- properties = schema .get ("properties" , {})
219- if not isinstance (properties , dict ):
220- return False
221-
222- # Validate each property
223- for _prop_name , prop_schema in properties .items ():
224- if not isinstance (prop_schema , dict ):
225- return False
226-
227- if "type" not in prop_schema :
228- return False
229-
230- return True
231-
199+ schema = model_class .model_json_schema ()
200+ return {
201+ "name" : model_class .__name__ ,
202+ "schema" : schema ,
203+ "fields" : list (schema .get ("properties" , {}).keys ()),
204+ "required" : schema .get ("required" , []),
205+ }
232206 except Exception as e :
233- logger .error ("Schema validation error: %s" , e )
234- return False
235-
236- @staticmethod
237- def get_schema_info (schema : Dict [str , Any ]) -> Dict [str , Any ]:
238- """Extract information about a schema for debugging/documentation.
239-
240- Args:
241- schema: JSON schema dictionary
242-
243- Returns:
244- Dictionary with schema information
245- """
246- info = {
247- "type" : schema .get ("type" ),
248- "properties_count" : len (schema .get ("properties" , {})),
249- "required_fields" : schema .get ("required" , []),
250- "has_nested_objects" : False ,
251- "has_arrays" : False ,
252- "has_enums" : False ,
253- }
254-
255- properties = schema .get ("properties" , {})
256- for prop_schema in properties .values ():
257- if prop_schema .get ("type" ) == "object" :
258- info ["has_nested_objects" ] = True
259- elif prop_schema .get ("type" ) == "array" :
260- info ["has_arrays" ] = True
261- elif "enum" in prop_schema :
262- info ["has_enums" ] = True
263-
264- return info
207+ logger .error ("Failed to get schema info for model '%s': %s" , model_class .__name__ , e )
208+ return {
209+ "name" : model_class .__name__ ,
210+ "schema" : {},
211+ "fields" : [],
212+ "required" : [],
213+ "error" : str (e ),
214+ }
0 commit comments