Skip to content

Commit 66be8bf

Browse files
committed
added fix for nasty bug in Fast_API__Route__Extractor where the 'BaseClass' wrappers where being used in the data extracted (instead of the correct types)
1 parent ab81fcb commit 66be8bf

14 files changed

Lines changed: 311 additions & 22 deletions

osbot_fast_api/api/routes/Type_Safe__Route__Analyzer.py renamed to osbot_fast_api/api/routes/type_safe/Type_Safe__Route__Analyzer.py

File renamed without changes.

osbot_fast_api/api/routes/Type_Safe__Route__Converter.py renamed to osbot_fast_api/api/routes/type_safe/Type_Safe__Route__Converter.py

File renamed without changes.

osbot_fast_api/api/routes/Type_Safe__Route__Registration.py renamed to osbot_fast_api/api/routes/type_safe/Type_Safe__Route__Registration.py

File renamed without changes.

osbot_fast_api/api/routes/Type_Safe__Route__Wrapper.py renamed to osbot_fast_api/api/routes/type_safe/Type_Safe__Route__Wrapper.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ def create_wrapper(self, function : Callable , # Origin
3131
if hasattr(function, '__route_path__'): # Also preserve route_path decorator if it exists
3232
wrapper_function.__route_path__ = function.__route_path__
3333

34+
if signature.type_safe_conversions or signature.primitive_conversions:
35+
wrapper_function.__original_param_types__ = {}
36+
for param_info in signature.parameters:
37+
wrapper_function.__original_param_types__[str(param_info.name)] = param_info.param_type
38+
39+
3440
return wrapper_function
3541

3642
@type_safe

osbot_fast_api/api/routes/type_safe/__init__.py

Whitespace-only changes.

osbot_fast_api/client/Fast_API__Route__Extractor.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,15 @@ def extract__query_params(self, route: APIRoute):
178178
@type_safe
179179
def extract__body_params(self, route: APIRoute):
180180
body_params = []
181+
original_types = getattr(route.endpoint, '__original_param_types__', {}) # todo: find a better way to communicate these mappings
182+
181183
for param in route.dependant.body_params:
182-
body_params.append(Schema__Endpoint__Param(name = param.name ,
183-
param_type = param.type_ ,
184-
required = param.required ,
184+
185+
param_type = original_types.get(param.name, param.type_) # Use original type if available, otherwise fall back to current behavior
186+
187+
body_params.append(Schema__Endpoint__Param(name = param.name,
188+
param_type = param_type, # Now the original Type_Safe class!
189+
required = param.required,
185190
description = param.field_info.description))
186191
return body_params
187192

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from unittest import TestCase
2+
from fastapi import FastAPI
3+
from starlette.testclient import TestClient
4+
from osbot_fast_api.client.Fast_API__Route__Extractor import Fast_API__Route__Extractor
5+
from osbot_utils.type_safe.Type_Safe import Type_Safe
6+
from osbot_utils.type_safe.primitives.domains.identifiers.Safe_Id import Safe_Id
7+
from osbot_fast_api.api.routes.Type_Safe__Route__Registration import Type_Safe__Route__Registration
8+
9+
10+
class Schema__Integration_User(Type_Safe): # Test schema for integration
11+
user_id : Safe_Id
12+
name : str
13+
age : int = 0
14+
15+
16+
class test_Fast_API__Type_Safe__routes_support(TestCase):
17+
18+
@classmethod
19+
def setUpClass(cls): # ONE-TIME setup for integration tests
20+
cls.app = FastAPI()
21+
cls.registration = Type_Safe__Route__Registration()
22+
cls.client = None # Will be created after routes registered
23+
24+
def test__full_cycle__original_types_preserved(self): # Test complete cycle: register -> extract -> serialize -> deserialize
25+
# Step 1: Register route with Type_Safe parameter
26+
def create_user(user: Schema__Integration_User) -> Schema__Integration_User:
27+
return user
28+
29+
self.registration.register_route(self.app.router, create_user, ['POST'])
30+
31+
# Step 2: Make actual request through TestClient
32+
if self.client is None:
33+
self.client = TestClient(self.app)
34+
35+
response = self.client.post('/create-user', json= { 'user_id': 'USER-123' ,
36+
'name' : 'Test User',
37+
'age' : 25 })
38+
39+
# Step 3: Verify response works correctly
40+
assert response.status_code == 200
41+
result = response.json()
42+
assert result['user_id'] == 'USER-123' # Type_Safe handled conversion
43+
assert result['name' ] == 'Test User'
44+
assert result['age' ] == 25
45+
46+
# Step 4: Extract routes and verify types
47+
extractor = Fast_API__Route__Extractor(app=self.app, include_default=False)
48+
collection = extractor.extract_routes()
49+
50+
# Find our route
51+
our_route = None # todo: we should have a helper method to provide this
52+
for route in collection.routes: # need to find a particular route
53+
if route.method_name == 'create_user':
54+
our_route = route
55+
break
56+
57+
assert our_route is not None
58+
59+
# Step 5: CRITICAL - Verify original types preserved throughout
60+
assert len(our_route.body_params) == 1
61+
assert our_route.body_params[0].param_type is Schema__Integration_User # Original class
62+
assert our_route.return_type is Schema__Integration_User # Original class
63+
64+
# Step 6: Serialize and deserialize
65+
json_data = collection.json()
66+
restored = collection.__class__.from_json(json_data)
67+
68+
# Step 7: Verify round-trip preserved everything
69+
assert collection.obj() == restored.obj()
70+
71+
# todo: add more edge cases tests

tests/unit/api/routes/test_Type_Safe__Route__Analyzer.py renamed to tests/unit/api/routes/type_safe/test_Type_Safe__Route__Analyzer.py

File renamed without changes.

tests/unit/api/routes/test_Type_Safe__Route__Converter.py renamed to tests/unit/api/routes/type_safe/test_Type_Safe__Route__Converter.py

File renamed without changes.

tests/unit/api/routes/test_Type_Safe__Route__Registration.py renamed to tests/unit/api/routes/type_safe/test_Type_Safe__Route__Registration.py

File renamed without changes.

0 commit comments

Comments
 (0)