Skip to content

Commit aacb87f

Browse files
committed
added support to Fast_API_Routes for Type_Safe__Primitive which is a major use case for post methods (major feature :) )
1 parent 1eab015 commit aacb87f

4 files changed

Lines changed: 204 additions & 71 deletions

File tree

osbot_fast_api/api/Fast_API_Routes.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from osbot_utils.decorators.lists.index_by import index_by
77
from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
88
from fastapi.exceptions import RequestValidationError
9+
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache
910
from osbot_fast_api.utils.type_safe.Type_Safe__To__BaseModel import type_safe__to__basemodel
1011

1112

@@ -31,15 +32,23 @@ def add_route_with_body(self, function, methods):
3132
sig = inspect.signature(function)
3233
type_hints = get_type_hints(function)
3334

34-
# Find Type_Safe parameters and convert them to BaseModel classes
3535
type_safe_conversions = {}
36+
primitive_field_types = {} # Track which fields are Type_Safe__Primitive
3637

3738
for param_name, param in sig.parameters.items():
3839
if param_name == 'self':
3940
continue
4041
param_type = type_hints.get(param_name)
4142
if param_type and inspect.isclass(param_type):
4243
if issubclass(param_type, Type_Safe) and not issubclass(param_type, Type_Safe__Primitive):
44+
45+
annotations = type_safe_cache.get_class_annotations(param_type) # For Type_Safe classes, also track their primitive fields
46+
for field_name, field_type in annotations:
47+
if isinstance(field_type, type) and issubclass(field_type, Type_Safe__Primitive):
48+
if param_name not in primitive_field_types:
49+
primitive_field_types[param_name] = {}
50+
primitive_field_types[param_name][field_name] = field_type
51+
4352
basemodel_class = type_safe__to__basemodel.convert_class(param_type)
4453
type_safe_conversions[param_name] = (param_type, basemodel_class)
4554

@@ -51,17 +60,27 @@ def wrapper(**kwargs):
5160
if param_name in type_safe_conversions:
5261
type_safe_class, _ = type_safe_conversions[param_name]
5362
if isinstance(param_value, dict):
63+
# Convert primitive fields back to Type_Safe__Primitive instances
64+
if param_name in primitive_field_types:
65+
for field_name, primitive_class in primitive_field_types[param_name].items():
66+
if field_name in param_value:
67+
param_value[field_name] = primitive_class(param_value[field_name])
5468
converted_kwargs[param_name] = type_safe_class(**param_value)
5569
else:
56-
data = param_value.model_dump() # Get the data from BaseModel
57-
converted_kwargs[param_name] = type_safe_class(**data) # Create instance of original Type_Safe class
70+
data = param_value.model_dump()
71+
# Convert primitive fields here too
72+
if param_name in primitive_field_types:
73+
for field_name, primitive_class in primitive_field_types[param_name].items():
74+
if field_name in data:
75+
data[field_name] = primitive_class(data[field_name])
76+
converted_kwargs[param_name] = type_safe_class(**data)
5877
else:
5978
converted_kwargs[param_name] = param_value
6079

6180
try:
6281
result = function(**converted_kwargs)
6382
except Exception as e:
64-
raise HTTPException(status_code=400, detail=f"{type(e).__name__}: {e}") # Convert business logic validation errors to HTTP 400
83+
raise HTTPException(status_code=400, detail=f"{type(e).__name__}: {e}")
6584

6685
if isinstance(result, Type_Safe):
6786
return type_safe__to__basemodel.convert_instance(result).model_dump()

osbot_fast_api/utils/type_safe/BaseModel__To__Type_Safe.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from typing import Type, Dict, Any, Optional, get_args, Union, List
2-
from osbot_utils.type_safe.decorators.type_safe import type_safe
3-
from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES
4-
from pydantic import BaseModel
5-
from pydantic_core import PydanticUndefined
6-
from osbot_utils.type_safe.Type_Safe import Type_Safe
7-
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache
1+
from typing import Type, Dict, Any, Optional, get_args, Union, List
2+
from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
3+
from osbot_utils.type_safe.decorators.type_safe import type_safe
4+
from osbot_utils.type_safe.shared.Type_Safe__Shared__Variables import IMMUTABLE_TYPES
5+
from pydantic import BaseModel
6+
from pydantic_core import PydanticUndefined
7+
from osbot_utils.type_safe.Type_Safe import Type_Safe
8+
from osbot_utils.type_safe.shared.Type_Safe__Cache import type_safe_cache
89

910

1011
class BaseModel__To__Type_Safe(Type_Safe):
@@ -220,6 +221,9 @@ def convert_nested_value(self, value : Any ,
220221
if value is None:
221222
return None
222223

224+
if isinstance(expected_type, type) and issubclass(expected_type, Type_Safe__Primitive):
225+
return expected_type(value) # Create Type_Safe__Primitive instance
226+
223227
if isinstance(expected_type, type) and issubclass(expected_type, BaseModel): # Nested BaseModel
224228
if isinstance(value, dict): # Dict representation
225229
nested_model = expected_type(**value) # Create BaseModel instance

osbot_fast_api/utils/type_safe/Type_Safe__To__BaseModel.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Type, Dict, Any, Optional, get_args, Union, List
2+
from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
23
from osbot_utils.type_safe.decorators.type_safe import type_safe
34
from pydantic import BaseModel, Field, create_model
45
from osbot_utils.type_safe.Type_Safe import Type_Safe
@@ -77,6 +78,11 @@ def convert_type(self, type_safe_type: Any # Type a
7778
return Optional[converted_args[0]]
7879
return Union[converted_args]
7980

81+
if isinstance(type_safe_type, type) and issubclass(type_safe_type, Type_Safe__Primitive): # Type_Safe__Primitive should map to its base primitive type for Pydantic
82+
if type_safe_type.__primitive_base__: # if __primitive_base__ is set
83+
return type_safe_type.__primitive_base__ # return it
84+
85+
8086
if isinstance(type_safe_type, type) and issubclass(type_safe_type, Type_Safe): # Handle Type_Safe classes
8187
return self.convert_class(type_safe_type) # Recursively convert
8288

@@ -105,7 +111,9 @@ def extract_instance_data(self, type_safe_instance: Type_Safe
105111
instance_locals = type_safe_instance.__locals__() # Get all fields from instance
106112

107113
for field_name, field_value in instance_locals.items():
108-
if isinstance(field_value, Type_Safe__List): # Convert Type_Safe collections
114+
if isinstance(field_value, Type_Safe__Primitive): # Convert Type_Safe__Primitive to its base type value
115+
data[field_name] = field_value.__primitive_base__(field_value)
116+
elif isinstance(field_value, Type_Safe__List): # Convert Type_Safe collections
109117
data[field_name] = self.convert_list(field_value)
110118
elif isinstance(field_value, Type_Safe__Dict):
111119
data[field_name] = self.convert_dict(field_value)

0 commit comments

Comments
 (0)