1- from typing import List
1+ from typing import List , Union
2+ from osbot_utils .type_safe .type_safe_core .collections .Type_Safe__List import Type_Safe__List
3+ from osbot_fast_api .schemas .consts__Fast_API import FAST_API_DEFAULT_ROUTES_PATHS
24from osbot_utils .type_safe .Type_Safe import Type_Safe
35from fastapi import FastAPI
4- from fastapi .routing import APIWebSocketRoute
6+ from fastapi .routing import APIWebSocketRoute , APIRoute , APIRouter
57from osbot_utils .type_safe .primitives .domains .identifiers .safe_str .Safe_Str__Id import Safe_Str__Id
68from osbot_utils .type_safe .type_safe_core .decorators .type_safe import type_safe
79from starlette .middleware .wsgi import WSGIMiddleware
8- from starlette .routing import Mount
10+ from starlette .routing import Mount , Route
911from starlette .staticfiles import StaticFiles
1012from osbot_fast_api .schemas .Safe_Str__Fast_API__Route__Prefix import Safe_Str__Fast_API__Route__Prefix
1113from osbot_fast_api .schemas .for_osbot_utils .enums .Enum__Http__Method import Enum__Http__Method
1214from osbot_fast_api .schemas .routes .Schema__Fast_API__Route import Schema__Fast_API__Route
1315from osbot_fast_api .schemas .routes .Schema__Fast_API__Routes__Collection import Schema__Fast_API__Routes__Collection
1416from osbot_fast_api .schemas .routes .enums .Enum__Route__Type import Enum__Route__Type
1517
16-
1718class Fast_API__Route__Extractor (Type_Safe ): # Dedicated class for route extraction
1819 app : FastAPI
1920 include_default : bool = False
2021 expand_mounts : bool = False
2122
2223 @type_safe
2324 def extract_routes (self ) -> Schema__Fast_API__Routes__Collection : # Main extraction method
24- routes = self .extract_routes_from_router (router = self .app ,
25+ routes = self .extract_routes_from_router (router = self .app . router ,
2526 route_prefix = Safe_Str__Fast_API__Route__Prefix ('/' ))
2627
2728 return Schema__Fast_API__Routes__Collection (routes = routes ,
2829 total_routes = len (routes ),
2930 has_mounts = any (r .is_mount for r in routes ),
3031 has_websockets = any (r .route_type == Enum__Route__Type .WEBSOCKET for r in routes ))
3132
32- def extract_routes_from_router (self , router , # Router to extract from
33+ @type_safe
34+ def extract_routes_from_router (self , router : APIRouter , # FastAPI to extract routes from
3335 route_prefix : Safe_Str__Fast_API__Route__Prefix
3436 ) -> List [Schema__Fast_API__Route ]: # Returns list of route schemas
3537 routes = []
3638
3739 for route in router .routes : # Skip default routes if requested
38- if not self .include_default and self ._is_default_route (route .path ):
40+ if not self .include_default and self .is_default_route (route .path ):
3941 continue
4042
41- # Build safe route path
42- full_path = self ._combine_paths (route_prefix , route .path )
43+ full_path = self ._combine_paths (route_prefix , route .path ) # Build safe route path
4344
44- # Extract based on route type
45- if isinstance (route , Mount ):
46- mount_routes = self ._extract_mount_routes (route , full_path )
45+ if isinstance (route , Mount ): # Extract based on route type
46+ mount_routes = self .extract_mount_routes (route , full_path )
4747 routes .extend (mount_routes )
4848 elif isinstance (route , APIWebSocketRoute ):
49- websocket_route = self ._create_websocket_route (route , full_path )
49+ websocket_route = self .create_websocket_route (route , full_path )
5050 routes .append (websocket_route )
5151 else :
52- api_route = self ._create_api_route (route , full_path )
52+ api_route = self .create_api_route (route , full_path )
5353 routes .append (api_route )
5454
5555 return routes
5656
57- def _create_api_route (self , route , # FastAPI route object
57+ @type_safe
58+ def create_api_route (self , route : Union [APIRoute , Route ] , # FastAPI route object
5859 path : Safe_Str__Fast_API__Route__Prefix
59- ) -> Schema__Fast_API__Route : # Returns route schema
60- # Convert methods to enum
61- http_methods = []
62- if hasattr (route , 'methods' ) and route .methods :
63- for method in sorted (route .methods ):
64- try :
65- http_methods .append (Enum__Http__Method (method ))
66- except ValueError :
67- pass # Skip unknown methods
68-
69- # Extract method name safely
70- method_name = Safe_Str__Id (route .name ) if route .name else Safe_Str__Id ("unnamed" )
71-
72- # Determine route class if from Routes__* pattern
73- route_class = self .extract_route_class (route )
74-
75- return Schema__Fast_API__Route (http_path = path ,
76- method_name = method_name ,
77- http_methods = http_methods ,
78- route_type = Enum__Route__Type .API_ROUTE ,
79- route_class = route_class ,
80- is_default = self ._is_default_route (str (path ) ))
81-
82- def _extract_mount_routes (self , mount , # Mount object
60+ ) -> Schema__Fast_API__Route : # Returns route schema
61+ http_methods = [] # Convert methods to enum
62+ for method in sorted (route .methods ):
63+ http_methods .append (Enum__Http__Method (method ))
64+ method_name = Safe_Str__Id (route .name )
65+ route_class = self .extract_route_class (route ) # Determine route class if from Routes__* pattern
66+ if type (route_class ) is APIRoute : # only the APIRoute class has the
67+ route_tags = route .tags # .tags method
68+ else :
69+ route_tags = None
70+ return Schema__Fast_API__Route (http_path = path ,
71+ method_name = method_name ,
72+ http_methods = http_methods ,
73+ route_type = Enum__Route__Type .API_ROUTE ,
74+ route_tags = route_tags ,
75+ route_class = route_class ,
76+ is_default = self .is_default_route (str (path )))
77+
78+ @type_safe
79+ def extract_mount_routes (self , mount : Mount , # Mount object
8380 path : Safe_Str__Fast_API__Route__Prefix
84- ) -> List [Schema__Fast_API__Route ]: # Returns route schemas
85- routes = []
81+ ) -> List [Schema__Fast_API__Route ]: # Returns route schemas
82+ routes = Type_Safe__List ( expected_type = Schema__Fast_API__Route )
8683
8784 # Determine mount type
8885 if isinstance (mount .app , WSGIMiddleware ):
89- route = Schema__Fast_API__Route (
90- http_path = path ,
91- method_name = Safe_Str__Id ("wsgi_app" ),
92- http_methods = [], # Unknown methods for WSGI
93- route_type = Enum__Route__Type .WSGI ,
94- is_mount = True
95- )
86+ route = Schema__Fast_API__Route (http_path = path ,
87+ method_name = Safe_Str__Id ("wsgi_app" ),
88+ http_methods = [] , # Unknown methods for WSGI
89+ route_type = Enum__Route__Type .WSGI ,
90+ is_mount = True )
9691 routes .append (route )
9792
9893 elif isinstance (mount .app , StaticFiles ):
99- route = Schema__Fast_API__Route (
100- http_path = path ,
101- method_name = Safe_Str__Id ("static_files" ),
102- http_methods = [Enum__Http__Method .GET , Enum__Http__Method .HEAD ],
103- route_type = Enum__Route__Type .STATIC ,
104- is_mount = True
105- )
94+ route = Schema__Fast_API__Route (http_path = path ,
95+ method_name = Safe_Str__Id ("static_files" ) ,
96+ http_methods = [Enum__Http__Method .GET , Enum__Http__Method .HEAD ],
97+ route_type = Enum__Route__Type .STATIC ,
98+ is_mount = True )
10699 routes .append (route )
107100
108- elif self .expand_mounts and hasattr (mount .app , 'router' ):
109- # Recursively extract routes from mounted app
110- mount_routes = self .extract_routes_from_router (
111- router = mount .app .router ,
112- route_prefix = path
113- )
101+ elif self .expand_mounts and hasattr (mount .app , 'router' ): # Recursively extract routes from mounted app
102+ mount_routes = self .extract_routes_from_router (router = mount .app .router ,
103+ route_prefix = path )
114104 routes .extend (mount_routes )
115- else :
116- # Generic mount
117- route = Schema__Fast_API__Route (
118- http_path = path ,
119- method_name = Safe_Str__Id ("mount" ),
120- http_methods = [],
121- route_type = Enum__Route__Type .MOUNT ,
122- is_mount = True
123- )
105+ else : # Generic mount
106+ route = Schema__Fast_API__Route (http_path = path ,
107+ method_name = Safe_Str__Id ("mount" ) ,
108+ http_methods = [] ,
109+ route_type = Enum__Route__Type .MOUNT ,
110+ is_mount = True )
124111 routes .append (route )
125112
126113 return routes
127114
128- def _create_websocket_route (self , route , # WebSocket route
129- path : Safe_Str__Fast_API__Route__Prefix
130- ) -> Schema__Fast_API__Route : # Returns route schema
131- return Schema__Fast_API__Route (
132- http_path = path ,
133- method_name = Safe_Str__Id (route .name ) if route .name else Safe_Str__Id ("websocket" ),
134- http_methods = [], # WebSockets don't use HTTP methods
135- route_type = Enum__Route__Type .WEBSOCKET
136- )
115+ @type_safe
116+ def create_websocket_route (self , route : APIWebSocketRoute , # WebSocket route
117+ path : Safe_Str__Fast_API__Route__Prefix
118+ ) -> Schema__Fast_API__Route : # Returns route schema
119+ return Schema__Fast_API__Route (http_path = path ,
120+ method_name = Safe_Str__Id (route .name ) , # if route.name else Safe_Str__Id("websocket"),
121+ http_methods = [] , # WebSockets don't use HTTP methods
122+ route_type = Enum__Route__Type .WEBSOCKET )
137123
138124 def _combine_paths (self , prefix : Safe_Str__Fast_API__Route__Prefix , # Prefix path
139125 path : str # Path to append
@@ -149,12 +135,10 @@ def _combine_paths(self, prefix : Safe_Str__Fast_API__Route__Prefix, # Prefix
149135
150136 return Safe_Str__Fast_API__Route__Prefix (combined )
151137
152- def _is_default_route (self , path : str ) -> bool : # Check if default route
153- # Import here to avoid circular dependency
154- from osbot_fast_api .schemas .consts__Fast_API import FAST_API_DEFAULT_ROUTES_PATHS
138+ def is_default_route (self , path : str ) -> bool : # Check if default route
155139 return path in FAST_API_DEFAULT_ROUTES_PATHS
156140
157- def extract_route_class (self , route ) -> Safe_Str__Id : # Extract class name (in most cases it will be something like Routes__* )
141+ def extract_route_class (self , route ) -> Safe_Str__Id : # Extract class name (in most cases it will be something like Routes__* )
158142 route_class = None
159143 if hasattr (route , 'endpoint' ):
160144 if hasattr (route .endpoint , '__self__' ): # first try to get the class name (if inside a class)
0 commit comments