11from typing import List , Union
22
3- from pydantic_core import PydanticUndefinedType , PydanticUndefined
3+ from pydantic_core import PydanticUndefined
44
5- from osbot_utils .utils .Dev import pprint
5+ from osbot_fast_api .schemas .Safe_Str__Fast_API__Route__Tag import Safe_Str__Fast_API__Route__Tag
6+ from osbot_utils .type_safe .primitives .domains .files .safe_str .Safe_Str__File__Path import Safe_Str__File__Path
67
7- from osbot_fast_api .client .schemas .Schema__Endpoint__Param import Schema__Endpoint__Param
8- from osbot_fast_api .client .schemas .enums .Enum__Param__Location import Enum__Param__Location
8+ from osbot_utils .utils .Http import url_join_safe
9+
10+ from osbot_fast_api .client .schemas .Schema__Endpoint__Param import Schema__Endpoint__Param
911from osbot_utils .type_safe .type_safe_core .collections .Type_Safe__List import Type_Safe__List
1012from osbot_fast_api .schemas .consts__Fast_API import FAST_API_DEFAULT_ROUTES_PATHS
1113from osbot_utils .type_safe .Type_Safe import Type_Safe
@@ -27,6 +29,61 @@ class Fast_API__Route__Extractor(Type_Safe): # Dedi
2729 include_default : bool = False
2830 expand_mounts : bool = False
2931
32+ @type_safe
33+ def create_api_route (self , route : Union [APIRoute , Route ] , # FastAPI route object
34+ path : Safe_Str__Fast_API__Route__Prefix
35+ ) -> Schema__Fast_API__Route : # Returns route schema
36+ http_methods = [] # Convert methods to enum
37+ for method in sorted (route .methods ):
38+ http_methods .append (Enum__Http__Method (method ))
39+ method_name = Safe_Str__Id (route .name )
40+ route_class = self .extract__route_class (route ) # Determine route class if from Routes__* pattern
41+ is_default = self .is_default_route (str (path ))
42+ description = None
43+ path_params = None
44+ query_params = None
45+ body_params = None
46+ return_type = None
47+ route_tags = None
48+
49+ if type (route ) is APIRoute : # only the APIRoute class has the
50+ description = route .description
51+ path_params = self .extract__path_params (route )
52+ query_params = self .extract__query_params (route )
53+ body_params = self .extract__body_params (route )
54+ return_type = self .extract__return_type (route )
55+ route_tags = route .tags
56+ route_type = Enum__Route__Type .API_ROUTE
57+ else :
58+ route_type = Enum__Route__Type .ROUTE
59+
60+ return Schema__Fast_API__Route (body_params = body_params ,
61+ description = description ,
62+ is_default = is_default ,
63+ http_path = path ,
64+ method_name = method_name ,
65+ http_methods = http_methods ,
66+ path_params = path_params ,
67+ query_params = query_params ,
68+ return_type = return_type ,
69+ route_type = route_type ,
70+ route_tags = route_tags ,
71+ route_class = route_class )
72+
73+ @type_safe
74+ def combine_paths (self , prefix : Safe_Str__Fast_API__Route__Prefix , # Prefix path
75+ path : Safe_Str__Fast_API__Route__Tag # Path to append
76+ ) -> Safe_Str__Fast_API__Route__Prefix : # Returns combined path
77+ prefix_str = str (prefix ).rstrip ('/' )
78+ path_str = path .lstrip ('/' )
79+
80+ if prefix_str == '' :
81+ combined = '/' + path_str
82+ else :
83+ combined = url_join_safe (prefix_str , path_str )
84+
85+ return Safe_Str__Fast_API__Route__Prefix (combined )
86+
3087 @type_safe
3188 def extract_routes (self ) -> Schema__Fast_API__Routes__Collection : # Main extraction method
3289 routes = self .extract_routes_from_router (router = self .app .router ,
@@ -47,7 +104,7 @@ def extract_routes_from_router(self, router : APIRouter
47104 if not self .include_default and self .is_default_route (route .path ):
48105 continue
49106
50- full_path = self ._combine_paths (route_prefix , route .path ) # Build safe route path
107+ full_path = self .combine_paths (route_prefix , route .path ) # Build safe route path
51108
52109 if isinstance (route , Mount ): # Extract based on route type
53110 mount_routes = self .extract_mount_routes (route , full_path )
@@ -61,29 +118,6 @@ def extract_routes_from_router(self, router : APIRouter
61118
62119 return routes
63120
64- @type_safe
65- def create_api_route (self , route : Union [APIRoute , Route ] , # FastAPI route object
66- path : Safe_Str__Fast_API__Route__Prefix
67- ) -> Schema__Fast_API__Route : # Returns route schema
68- http_methods = [] # Convert methods to enum
69- for method in sorted (route .methods ):
70- http_methods .append (Enum__Http__Method (method ))
71- method_name = Safe_Str__Id (route .name )
72- route_class = self .extract__route_class (route ) # Determine route class if from Routes__* pattern
73- path_params = self .extract__path_params (route = route )
74- if type (route_class ) is APIRoute : # only the APIRoute class has the
75- route_tags = route .tags # .tags method
76- else :
77- route_tags = None
78- return Schema__Fast_API__Route (http_path = path ,
79- method_name = method_name ,
80- http_methods = http_methods ,
81- route_type = Enum__Route__Type .API_ROUTE ,
82- path_params = path_params ,
83- route_tags = route_tags ,
84- route_class = route_class ,
85- is_default = self .is_default_route (str (path )))
86-
87121 @type_safe
88122 def extract_mount_routes (self , mount : Mount , # Mount object
89123 path : Safe_Str__Fast_API__Route__Prefix
@@ -125,12 +159,46 @@ def extract_mount_routes(self, mount: Mount , # Mo
125159 def extract__path_params (self , route : APIRoute ):
126160 path_params = []
127161 for param in route .dependant .path_params :
128- path_params .append (Schema__Endpoint__Param (name = param .name ,
129- location = Enum__Param__Location .PATH ,
130- param_type = param .type_ ,
131- required = param .required ))
162+ path_params .append (Schema__Endpoint__Param (name = param .name ,
163+ description = param .field_info .description ,
164+ param_type = param .type_ ))
132165 return path_params
133166
167+ @type_safe
168+ def extract__query_params (self , route : APIRoute ):
169+ query_params = []
170+ for param in route .dependant .query_params :
171+ if param .default is PydanticUndefined :
172+ default_value = None
173+ else :
174+ default_value = param .default
175+ query_params .append (Schema__Endpoint__Param (default = default_value ,
176+ name = param .name ,
177+ param_type = param .type_ ,
178+ required = param .required ,
179+ description = param .field_info .description ))
180+ return query_params
181+
182+ @type_safe
183+ def extract__body_params (self , route : APIRoute ):
184+ body_params = []
185+ for param in route .dependant .body_params :
186+ body_params .append (Schema__Endpoint__Param (name = param .name ,
187+ param_type = param .type_ ,
188+ required = param .required ,
189+ description = param .field_info .description ))
190+ return body_params
191+
192+ @type_safe
193+ def extract__return_type (self , route : APIRoute ):
194+ # Get return type from endpoint callable
195+ if hasattr (route , 'endpoint' ) and route .endpoint :
196+ import inspect
197+ sig = inspect .signature (route .endpoint )
198+ if sig .return_annotation != inspect .Parameter .empty :
199+ return sig .return_annotation
200+ return None
201+
134202 @type_safe
135203 def create_websocket_route (self , route : APIWebSocketRoute , # WebSocket route
136204 path : Safe_Str__Fast_API__Route__Prefix
@@ -140,30 +208,17 @@ def create_websocket_route(self, route : APIWebSocketRoute
140208 http_methods = [] , # WebSockets don't use HTTP methods
141209 route_type = Enum__Route__Type .WEBSOCKET )
142210
143- def _combine_paths (self , prefix : Safe_Str__Fast_API__Route__Prefix , # Prefix path
144- path : str # Path to append
145- ) -> Safe_Str__Fast_API__Route__Prefix : # Returns combined path
146- # Handle path combination safely
147- prefix_str = str (prefix ).rstrip ('/' )
148- path_str = path .lstrip ('/' )
149-
150- if prefix_str == '' :
151- combined = '/' + path_str
152- else :
153- combined = f"{ prefix_str } /{ path_str } "
154211
155- return Safe_Str__Fast_API__Route__Prefix (combined )
156-
157- def is_default_route (self , path : str ) -> bool : # Check if default route
158- return path in FAST_API_DEFAULT_ROUTES_PATHS
159-
160- def extract__route_class (self , route ) -> Safe_Str__Id : # Extract class name (in most cases it will be something like Routes__* )
212+ def extract__route_class (self , route ) -> Safe_Str__Id : # Extract class name (in most cases it will be something like Routes__* )
161213 route_class = None
162214 if hasattr (route , 'endpoint' ):
163- if hasattr (route .endpoint , '__self__' ): # first try to get the class name (if inside a class)
215+ if hasattr (route .endpoint , '__self__' ): # first try to get the class name (if inside a class)
164216 route_class = Safe_Str__Id (route .endpoint .__self__ .__class__ .__name__ )
165- elif hasattr (route .endpoint , '__qualname__' ): # then if that is not available use __qualname__
217+ elif hasattr (route .endpoint , '__qualname__' ): # then if that is not available use __qualname__
166218 qualname = route .endpoint .__qualname__
167- if '.' in qualname : # todo: see if there is a better way to do this and find the base class name
219+ if '.' in qualname : # todo: see if there is a better way to do this and find the base class name
168220 route_class = qualname .split ('.' )[0 ]
169- return Safe_Str__Id (route_class )
221+ return Safe_Str__Id (route_class )
222+
223+ def is_default_route (self , path : str ) -> bool : # Check if default route
224+ return path in FAST_API_DEFAULT_ROUTES_PATHS
0 commit comments