1+ import functools
12import inspect
2- from typing import get_type_hints
3- from fastapi import APIRouter , FastAPI
4- from osbot_utils .type_safe .Type_Safe import Type_Safe
5- from osbot_utils .decorators .lists .index_by import index_by
3+ from typing import get_type_hints
4+ from fastapi import APIRouter , FastAPI
5+ from osbot_utils .type_safe .Type_Safe import Type_Safe
6+ from osbot_utils .decorators .lists .index_by import index_by
7+ from osbot_utils .type_safe .Type_Safe__Primitive import Type_Safe__Primitive
8+ from fastapi .exceptions import RequestValidationError
9+ from osbot_utils .utils .Dev import pprint
10+ from pydantic_core import ErrorDetails
11+
12+ from osbot_fast_api .utils .type_safe .BaseModel__To__Type_Safe import basemodel__to__type_safe
13+ from osbot_fast_api .utils .type_safe .Type_Safe__To__BaseModel import type_safe__to__basemodel
14+
615
716class Fast_API_Routes (Type_Safe ): # refactor to Fast_API__Routes
817 router : APIRouter
@@ -22,53 +31,170 @@ def add_route(self,function, methods):
2231 self .router .add_api_route (path = path , endpoint = function , methods = methods )
2332 return self
2433
25- def add_route_delete (self , function ):
26- return self .add_route (function = function , methods = ['DELETE' ])
27-
28- def add_route_get (self , function ):
29- return self .add_route (function = function , methods = ['GET' ])
30-
31- def add_route_post (self , function ): # add post with support for Type_Safe objects
32- sig = inspect .signature (function ) # Check if function has a Type_Safe parameter
34+ def add_route_with_body (self , function , methods ):
35+ sig = inspect .signature (function )
3336 type_hints = get_type_hints (function )
3437
35- type_safe_param = False
38+ # Find Type_Safe parameters and convert them to BaseModel classes
39+ type_safe_conversions = {}
40+
3641 for param_name , param in sig .parameters .items ():
3742 if param_name == 'self' :
3843 continue
3944 param_type = type_hints .get (param_name )
4045 if param_type and inspect .isclass (param_type ):
41- if issubclass (param_type , Type_Safe ):
42- type_safe_param = True
43- break
44-
45- if type_safe_param :
46- def wrapper (data : dict ):
47- param_object = param_type .from_json (data )
48- kwargs = { param_name : param_object }
49- result = function (** kwargs )
46+ if issubclass (param_type , Type_Safe ) and not issubclass (param_type , Type_Safe__Primitive ):
47+ basemodel_class = type_safe__to__basemodel .convert_class (param_type )
48+ type_safe_conversions [param_name ] = (param_type , basemodel_class )
49+
50+ if type_safe_conversions :
51+ @functools .wraps (function )
52+ def wrapper (** kwargs ):
53+ converted_kwargs = {}
54+ for param_name , param_value in kwargs .items ():
55+ if param_name in type_safe_conversions :
56+ type_safe_class , _ = type_safe_conversions [param_name ]
57+ if isinstance (param_value , dict ):
58+ converted_kwargs [param_name ] = type_safe_class (** param_value )
59+ else :
60+ data = param_value .model_dump () # Get the data from BaseModel
61+ converted_kwargs [param_name ] = type_safe_class (** data ) # Create instance of original Type_Safe class
62+ else :
63+ converted_kwargs [param_name ] = param_value
64+
65+ result = function (** converted_kwargs )
66+
5067 if isinstance (result , Type_Safe ):
51- return result . json ()
68+ return type_safe__to__basemodel . convert_instance ( result ). model_dump ()
5269 return result
5370
71+ # Build new parameters with BaseModel types
72+ new_params = []
73+ for param_name , param in sig .parameters .items ():
74+ if param_name == 'self' :
75+ continue
76+ if param_name in type_safe_conversions :
77+ _ , basemodel_class = type_safe_conversions [param_name ]
78+ new_params .append (inspect .Parameter (
79+ name = param_name ,
80+ kind = param .kind ,
81+ default = param .default ,
82+ annotation = basemodel_class
83+ ))
84+ else :
85+ new_params .append (param )
5486
55- # todo: the code below is not working (need to add support for supporting Type_Safe return values)
56- # Remove the return type annotation to prevent FastAPI validation
57- # wrapper.__annotations__ = function.__annotations__.copy()
58- # if 'return' in wrapper.__annotations__:
59- # del wrapper.__annotations__['return'] # Remove return type so FastAPI doesn't validate
87+ # Set the new signature on the wrapper
88+ wrapper .__signature__ = inspect .Signature (parameters = new_params )
6089
90+ # Also update annotations for FastAPI
91+ wrapper .__annotations__ = {}
92+ for param_name , param_type in type_hints .items ():
93+ if param_name in type_safe_conversions :
94+ _ , basemodel_class = type_safe_conversions [param_name ]
95+ wrapper .__annotations__ [param_name ] = basemodel_class
96+ else :
97+ wrapper .__annotations__ [param_name ] = param_type
6198
62- #path = '/' + function.__name__.replace('_', '-')
6399 path = self .parse_function_name (function .__name__ )
64- self .router .add_api_route (path = path , endpoint = wrapper , methods = [ 'POST' ] )
100+ self .router .add_api_route (path = path , endpoint = wrapper , methods = methods )
65101 return self
66102 else :
67- # Normal route
68- return self .add_route (function = function , methods = ['POST' ])
103+ return self .add_route (function = function , methods = methods )
104+
105+ def add_route_delete (self , function ):
106+ #return self.add_route(function=function, methods=['DELETE'])
107+
108+ return self .add_route_with_body (function , methods = ['DELETE' ])
109+
110+ def add_route_get (self , function ):
111+ import functools
112+ sig = inspect .signature (function )
113+ type_hints = get_type_hints (function )
114+
115+ primitive_conversions = {} # Check for Type_Safe__Primitive parameters
116+
117+ for param_name , param in sig .parameters .items ():
118+ if param_name == 'self' :
119+ continue
120+ param_type = type_hints .get (param_name )
121+ if param_type and inspect .isclass (param_type ):
122+ if issubclass (param_type , Type_Safe__Primitive ):
123+ primitive_base = param_type .__primitive_base__
124+ if primitive_base is None :
125+ for base in param_type .__mro__ :
126+ if base in (str , int , float ):
127+ primitive_base = base
128+ break
129+
130+ if primitive_base :
131+ primitive_conversions [param_name ] = (param_type , primitive_base )
132+
133+ if primitive_conversions :
134+ # Create a wrapper that preserves the exact signature
135+ @functools .wraps (function )
136+ def wrapper (* args , ** kwargs ):
137+ # Convert primitive values to Type_Safe__Primitive instances
138+ converted_kwargs = {}
139+ validation_errors = []
140+ for param_name , param_value in kwargs .items ():
141+ if param_name in primitive_conversions :
142+ type_safe_primitive_class , _ = primitive_conversions [param_name ]
143+ try :
144+ converted_kwargs [param_name ] = type_safe_primitive_class (param_value )
145+ except (ValueError , TypeError ) as e :
146+ # Create validation error in FastAPI format
147+ validation_errors .append ({'type' : 'value_error' ,
148+ 'loc' : ('query' , param_name ),
149+ 'msg' : str (e ),
150+ 'input' : param_value })
151+ else :
152+ converted_kwargs [param_name ] = param_value
153+
154+ # If there were validation errors, raise them
155+ if validation_errors :
156+ raise RequestValidationError (validation_errors )
157+
158+ # Call with self if it's in args
159+ if args :
160+ result = function (* args , ** converted_kwargs )
161+ else :
162+ result = function (** converted_kwargs )
163+
164+ # Convert result if needed
165+ if isinstance (result , Type_Safe__Primitive ):
166+ return result .__primitive_base__ (result )
167+ return result
168+
169+ # Build new parameter list with primitive types
170+ new_params = []
171+ for param_name , param in sig .parameters .items ():
172+ if param_name == 'self' :
173+ continue # Skip self
174+ if param_name in primitive_conversions :
175+ _ , primitive_type = primitive_conversions [param_name ]
176+ # Replace with primitive type parameter
177+ new_params .append (inspect .Parameter (
178+ name = param_name ,
179+ kind = param .kind ,
180+ default = param .default ,
181+ annotation = primitive_type
182+ ))
183+ else :
184+ new_params .append (param )
185+
186+ # Create new signature
187+ wrapper .__signature__ = inspect .Signature (parameters = new_params )
188+
189+ return self .add_route (function = wrapper , methods = ['GET' ])
190+ else :
191+ return self .add_route (function = function , methods = ['GET' ])
192+
193+ def add_route_post (self , function ):
194+ return self .add_route_with_body (function , methods = ['POST' ])
69195
70196 def add_route_put (self , function ):
71- return self .add_route ( function = function , methods = ['PUT' ])
197+ return self .add_route_with_body ( function , methods = ['PUT' ])
72198
73199 def fast_api_utils (self ):
74200 from osbot_fast_api .utils .Fast_API_Utils import Fast_API_Utils
0 commit comments