4444from trpc_agent_sdk .types import GroundingMetadata
4545from trpc_agent_sdk .types import GenerateContentResponseUsageMetadata
4646
47+ LoadDialectHook = Callable [[TypeDecorator , Dialect ], Any ]
48+ ProcessBindHook = Callable [[TypeDecorator , Any , Dialect ], Any ]
49+ ProcessResultHook = Callable [[TypeDecorator , Any , Dialect ], Any ]
50+
51+
52+ class TypeDecoratorHookRegistry :
53+ """Global hook registry for SQLAlchemy TypeDecorator callbacks."""
54+
55+ _load_dialect_hooks : dict [type [TypeDecorator ], list [LoadDialectHook ]] = {}
56+ _process_bind_hooks : dict [type [TypeDecorator ], list [ProcessBindHook ]] = {}
57+ _process_result_hooks : dict [type [TypeDecorator ], list [ProcessResultHook ]] = {}
58+
59+ @classmethod
60+ def register_load_dialect_hook (cls , decorator_cls : type [TypeDecorator ], hook : LoadDialectHook ) -> None :
61+ """Register hook for ``load_dialect_impl``."""
62+ cls ._load_dialect_hooks .setdefault (decorator_cls , []).append (hook )
63+
64+ @classmethod
65+ def register_process_bind_hook (cls , decorator_cls : type [TypeDecorator ], hook : ProcessBindHook ) -> None :
66+ """Register hook for ``process_bind_param``."""
67+ cls ._process_bind_hooks .setdefault (decorator_cls , []).append (hook )
68+
69+ @classmethod
70+ def register_process_result_hook (cls , decorator_cls : type [TypeDecorator ], hook : ProcessResultHook ) -> None :
71+ """Register hook for ``process_result_value``."""
72+ cls ._process_result_hooks .setdefault (decorator_cls , []).append (hook )
73+
74+ @classmethod
75+ def run_load_dialect_hooks (cls , decorator : TypeDecorator , dialect : Dialect ) -> Any :
76+ """Run load hooks and return first override result."""
77+ for hook in cls ._load_dialect_hooks .get (type (decorator ), []):
78+ result = hook (decorator , dialect )
79+ if result is not None :
80+ return result
81+ return None
82+
83+ @classmethod
84+ def run_process_bind_hooks (cls , decorator : TypeDecorator , value : Any , dialect : Dialect ) -> Any :
85+ """Run bind hooks and return first override result."""
86+ for hook in cls ._process_bind_hooks .get (type (decorator ), []):
87+ result = hook (decorator , value , dialect )
88+ if result is not None :
89+ return result
90+ return None
91+
92+ @classmethod
93+ def run_process_result_hooks (cls , decorator : TypeDecorator , value : Any , dialect : Dialect ) -> Any :
94+ """Run result hooks and return first override result."""
95+ for hook in cls ._process_result_hooks .get (type (decorator ), []):
96+ result = hook (decorator , value , dialect )
97+ if result is not None :
98+ return result
99+ return None
100+
101+
102+ # Global class object used as unified registration entry.
103+ GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY = TypeDecoratorHookRegistry
104+
47105
48106def decode_content (content : Optional [dict [str , Any ]]) -> Optional [Content ]:
49107 """Decode a content object from a JSON dictionary.
@@ -119,13 +177,19 @@ class DynamicJSON(TypeDecorator):
119177 impl = Text # Default implementation is TEXT
120178
121179 def load_dialect_impl (self , dialect : Dialect ) -> TypeDecorator :
180+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_load_dialect_hooks (self , dialect )
181+ if hook_result is not None :
182+ return hook_result
122183 if dialect .name == "postgresql" :
123184 return dialect .type_descriptor (postgresql .JSONB ) # type: ignore
124185 if dialect .name == "mysql" :
125186 return dialect .type_descriptor (mysql .LONGTEXT ) # type: ignore
126187 return dialect .type_descriptor (Text ) # Default to Text for other dialects # type: ignore
127188
128189 def process_bind_param (self , value : Any , dialect : Dialect ) -> Any :
190+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_bind_hooks (self , value , dialect )
191+ if hook_result is not None :
192+ return hook_result
129193 if value is not None :
130194 if dialect .name == "postgresql" :
131195 return value # JSONB handles dict directly
@@ -134,6 +198,9 @@ def process_bind_param(self, value: Any, dialect: Dialect) -> Any:
134198 return value
135199
136200 def process_result_value (self , value : Any , dialect : Dialect ) -> Any :
201+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_result_hooks (self , value , dialect )
202+ if hook_result is not None :
203+ return hook_result
137204 if value is not None :
138205 if dialect .name == "postgresql" :
139206 return value # JSONB returns dict directly
@@ -157,6 +224,9 @@ def __init__(self, length: Optional[int] = None, *args: Any, **kwargs: Any) -> N
157224 self .length = length
158225
159226 def load_dialect_impl (self , dialect : Dialect ) -> TypeDecorator :
227+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_load_dialect_hooks (self , dialect )
228+ if hook_result is not None :
229+ return hook_result
160230 if dialect .name == "mysql" :
161231 # Use VARCHAR with utf8mb4 charset and utf8mb4_unicode_ci collation
162232 return dialect .type_descriptor (mysql .VARCHAR (self .length , charset = 'utf8mb4' ,
@@ -166,6 +236,18 @@ def load_dialect_impl(self, dialect: Dialect) -> TypeDecorator:
166236 return dialect .type_descriptor (String (self .length ))
167237 return dialect .type_descriptor (String ())
168238
239+ def process_bind_param (self , value : Any , dialect : Dialect ) -> Any :
240+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_bind_hooks (self , value , dialect )
241+ if hook_result is not None :
242+ return hook_result
243+ return value
244+
245+ def process_result_value (self , value : Any , dialect : Dialect ) -> Any :
246+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_result_hooks (self , value , dialect )
247+ if hook_result is not None :
248+ return hook_result
249+ return value
250+
169251
170252class PreciseTimestamp (TypeDecorator ):
171253 """Represents a timestamp precise to the microsecond."""
@@ -174,30 +256,54 @@ class PreciseTimestamp(TypeDecorator):
174256 cache_ok = True
175257
176258 def load_dialect_impl (self , dialect : Dialect ) -> TypeDecorator :
259+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_load_dialect_hooks (self , dialect )
260+ if hook_result is not None :
261+ return hook_result
177262 if dialect .name == "mysql" :
178263 return dialect .type_descriptor (mysql .DATETIME (fsp = 6 ))
179264 return self .impl
180265
266+ def process_bind_param (self , value : Any , dialect : Dialect ) -> Any :
267+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_bind_hooks (self , value , dialect )
268+ if hook_result is not None :
269+ return hook_result
270+ return value
271+
272+ def process_result_value (self , value : Any , dialect : Dialect ) -> Any :
273+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_result_hooks (self , value , dialect )
274+ if hook_result is not None :
275+ return hook_result
276+ return value
277+
181278
182279class DynamicPickleType (TypeDecorator ):
183280 """Represents a type that can be pickled."""
184281
185282 impl = PickleType
186283
187284 def load_dialect_impl (self , dialect : Dialect ) -> TypeDecorator :
285+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_load_dialect_hooks (self , dialect )
286+ if hook_result is not None :
287+ return hook_result
188288 if dialect .name == "spanner+spanner" :
189289 return dialect .type_descriptor (SpannerPickleType ) # type: ignore
190290 if dialect .name == "mysql" :
191291 return dialect .type_descriptor (mysql .LONGBLOB ) # type: ignore
192292 return self .impl
193293
194294 def process_bind_param (self , value : Any , dialect : Dialect ) -> Any :
295+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_bind_hooks (self , value , dialect )
296+ if hook_result is not None :
297+ return hook_result
195298 if value is not None :
196299 if dialect .name == "spanner+spanner" :
197300 return pickle .dumps (value )
198301 return value
199302
200303 def process_result_value (self , value : Any , dialect : Dialect ) -> Any :
304+ hook_result = GLOBAL_TYPE_DECORATOR_HOOK_REGISTRY .run_process_result_hooks (self , value , dialect )
305+ if hook_result is not None :
306+ return hook_result
201307 if value is not None :
202308 if dialect .name == "spanner+spanner" :
203309 return pickle .loads (value )
0 commit comments