11# Copyright INRIM (https://www.inrim.eu)
22# See LICENSE file for full licensing details.
33import pydantic
4+ from pydantic .v1 .fields import ModelField
45
56from .BaseClass import PluginBase
67from .QueryEngine import QueryEngine , DateTimeEncoder
@@ -165,6 +166,74 @@ async def component_by_type(self, model_type):
165166 async def component_distinct_model (self ):
166167 return await search_distinct (Component )
167168
169+ def _decode_mongo_query (self , query : Any , model_cls : Type [ModelType ]):
170+ DT_OPS = {
171+ "$eq" , "$ne" , "$gt" , "$gte" , "$lt" , "$lte" ,
172+ "$in" , "$nin" ,
173+ }
174+
175+ LOGICAL_OPS = {"$and" , "$or" , "$nor" }
176+
177+ def parse_dt (v : str ) -> datetime | str :
178+ # parse ISO-ish strings; keep original if not parseable
179+ s = v .strip ()
180+ if s .endswith ("Z" ):
181+ s = s [:- 1 ] + "+00:00"
182+ try :
183+ return datetime .fromisoformat (s )
184+ except ValueError :
185+ return v
186+
187+ def is_datetime_field (field : ModelField | None ) -> bool :
188+ # Pydantic v1: Optional[datetime] still has type_ == datetime
189+ return bool (field and field .type_ is datetime )
190+
191+ fields = model_cls .__fields__
192+
193+ def walk (node : Any , * , force_dt : bool = False ) -> Any :
194+ # force_dt means: we are inside a datetime field filter subtree
195+ if isinstance (node , dict ):
196+ out : dict [str , Any ] = {}
197+ for k , v in node .items ():
198+ # logical operators contain subfilters
199+ if k in LOGICAL_OPS and isinstance (v , list ):
200+ out [k ] = [walk (item , force_dt = False ) for item in v ]
201+ continue
202+
203+ # Mongo operators (when inside a datetime field)
204+ if force_dt and k in DT_OPS :
205+ if isinstance (v , str ):
206+ out [k ] = parse_dt (v )
207+ elif isinstance (v , list ):
208+ out [k ] = [parse_dt (x ) if isinstance (x , str ) else x for x in v ]
209+ else :
210+ out [k ] = v
211+ continue
212+
213+ # Normal field at root / nested document style
214+ field = fields .get (k )
215+ if is_datetime_field (field ):
216+ # Under a datetime field: parse direct strings, and also operator dict values
217+ if isinstance (v , str ):
218+ out [k ] = parse_dt (v )
219+ else :
220+ out [k ] = walk (v , force_dt = True )
221+ else :
222+ out [k ] = walk (v , force_dt = False )
223+ return out
224+
225+ if isinstance (node , list ):
226+ return [walk (x , force_dt = force_dt ) for x in node ]
227+
228+ # primitives
229+ if force_dt and isinstance (node , str ):
230+ return parse_dt (node )
231+ return node
232+
233+ res = walk (query , force_dt = False )
234+ logger .debug (f"[_decode_mongo_query]: { res } ]" )
235+ return res
236+
168237 async def search_base (
169238 self ,
170239 data_model : Type [ModelType ],
@@ -185,6 +254,9 @@ async def search_base(
185254 #
186255 sort = [("list_order" , ASCENDING ), ("rec_name" , DESCENDING )]
187256
257+ if query :
258+ query = self ._decode_mongo_query (query , data_model )
259+
188260 if use_aggregate :
189261 list_data = await aggregate (
190262 data_model , query , sort = sort , limit = limit , skip = skip
@@ -248,6 +320,10 @@ async def count_by_filter(
248320 model = data_model
249321 if not isinstance (data_model , str ):
250322 model = data_model .str_name ()
323+
324+ if query :
325+ query = self ._decode_mongo_query (query , data_model )
326+
251327 return await count_by_filter (model , domain = query )
252328
253329 async def search (
0 commit comments