Skip to content

Commit dd99c27

Browse files
committed
Major improvements to Fast_API__Route__Extractor, namely to the schemas Schema__Endpoint__Param and Schema__Fast_API__Route
1 parent b103452 commit dd99c27

14 files changed

Lines changed: 2462 additions & 668 deletions

osbot_fast_api/client/Fast_API__Contract__Extractor.py

Lines changed: 237 additions & 237 deletions
Large diffs are not rendered by default.

osbot_fast_api/client/Fast_API__Route__Extractor.py

Lines changed: 108 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from 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
911
from osbot_utils.type_safe.type_safe_core.collections.Type_Safe__List import Type_Safe__List
1012
from osbot_fast_api.schemas.consts__Fast_API import FAST_API_DEFAULT_ROUTES_PATHS
1113
from 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

osbot_fast_api/client/schemas/Schema__Endpoint__Contract.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from typing import List
1+
from typing import List, Type
22
from osbot_fast_api.schemas.for_osbot_utils.enums.Enum__Http__Method import Enum__Http__Method
33
from osbot_utils.type_safe.Type_Safe import Type_Safe
44
from osbot_utils.type_safe.primitives.core.Safe_UInt import Safe_UInt
55
from osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id import Safe_Str__Id
66
from osbot_fast_api.client.schemas.Schema__Endpoint__Param import Schema__Endpoint__Param
7-
from osbot_fast_api.client.schemas.safe_str.Safe_Str__Python_Type import Safe_Str__Python__Type
87
from osbot_fast_api.schemas.Safe_Str__Fast_API__Route__Prefix import Safe_Str__Fast_API__Route__Prefix
98

109

@@ -26,8 +25,8 @@ class Schema__Endpoint__Contract(Type_Safe):
2625
header_params: List[Schema__Endpoint__Param]
2726

2827
# Request/Response schemas - Type names as strings for serialization
29-
request_schema : Safe_Str__Python__Type = None # Request body Type_Safe class name
30-
response_schema: Safe_Str__Python__Type = None # Response Type_Safe class name
28+
request_schema : Type = None # Request body Type_Safe class name
29+
response_schema: Type = None # Response Type_Safe class name
3130

3231
# Error handling - from AST analysis
3332
error_codes: List[Safe_UInt] # Status codes raised (excluding 400/422) # todo: we should be using Safe_UInt__Http__Error_Codes here
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from typing import Any, Type
1+
from typing import Type, Any
22
from osbot_utils.type_safe.Type_Safe import Type_Safe
3+
from osbot_utils.type_safe.primitives.domains.common.safe_str.Safe_Str__Text import Safe_Str__Text
34
from osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Id import Safe_Str__Id
4-
from osbot_fast_api.client.schemas.enums.Enum__Param__Location import Enum__Param__Location
55

66
class Schema__Endpoint__Param(Type_Safe):
7-
name : Safe_Str__Id # Parameter name
8-
location : Enum__Param__Location # Where parameter appears
7+
default : Any = None # Default value if provided
8+
description : Safe_Str__Text = None # Description if provided
9+
name : Safe_Str__Id # Parameter name
910
param_type : Type
10-
required : bool = True # Is parameter required?
11+
required : bool = True # Only meaningful for query/body params
12+

osbot_fast_api/client/schemas/safe_str/Safe_Str__Python_Type.py

Lines changed: 0 additions & 16 deletions
This file was deleted.

osbot_fast_api/schemas/routes/Schema__Fast_API__Route.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import List
1+
from typing import List, Type
2+
3+
from osbot_utils.type_safe.primitives.domains.common.safe_str.Safe_Str__Text import Safe_Str__Text
24

35
from osbot_fast_api.client.schemas.Schema__Endpoint__Param import Schema__Endpoint__Param
46
from osbot_utils.type_safe.Type_Safe import Type_Safe
@@ -10,13 +12,24 @@
1012

1113

1214
class Schema__Fast_API__Route(Type_Safe): # Single route information
15+
body_params : List[Schema__Endpoint__Param] = None # or body_schema
16+
description : Safe_Str__Text # endpoint description from docstring
1317
http_path : Safe_Str__Fast_API__Route__Prefix # The actual HTTP path
1418
http_methods : List[Enum__Http__Method] # HTTP methods supported
1519
is_default : bool = False # Is this a default FastAPI route
1620
is_mount : bool = False # Is this a mount point
1721
method_name : Safe_Str__Id = None # Method/function name
22+
path_params : List[Schema__Endpoint__Param] = None
23+
query_params : List[Schema__Endpoint__Param] = None
24+
return_type : Type = None
1825
route_type : Enum__Route__Type = Enum__Route__Type.API_ROUTE
1926
route_class : Safe_Str__Id = None # Class name if from Routes__* class
2027
route_tags : List[Safe_Str__Fast_API__Route__Tag] = None # Route tag/category
21-
path_params : List[Schema__Endpoint__Param ] = None
28+
29+
30+
31+
32+
33+
34+
2235

osbot_fast_api/schemas/routes/enums/Enum__Route__Type.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
class Enum__Route__Type(str, Enum):
55
API_ROUTE = "api_route"
66
MOUNT = "mount"
7-
WEBSOCKET = "websocket"
7+
ROUTE = "route"
88
STATIC = "static"
9+
WEBSOCKET = "websocket"
910
WSGI = "wsgi"

0 commit comments

Comments
 (0)