@@ -9,7 +9,8 @@ use pyo3::BoundObject;
99use std:: panic:: { self , AssertUnwindSafe } ;
1010
1111use chrono:: { DateTime , Duration as ChronoDuration , Offset , TimeZone } ;
12- use pyo3:: types:: { PyBool , PyBytes , PyDict , PyList , PyTuple , PyType } ;
12+ use pyo3:: types:: { PyBool , PyBytes , PyDict , PyList , PyMapping , PyTuple , PyType , PyTypeMethods } ;
13+ use pyo3:: PyTypeInfo ;
1314
1415use std:: collections:: HashMap ;
1516use std:: error:: Error ;
@@ -271,6 +272,50 @@ impl fmt::Display for CelError {
271272}
272273impl Error for CelError { }
273274
275+ impl < ' a > RustyPyType < ' a > {
276+ fn key_from_py ( key : & Bound < ' _ , PyAny > ) -> Result < Key , CelError > {
277+ if key. is_none ( ) {
278+ return Err ( CelError :: ConversionError (
279+ "None cannot be used as a key in dictionaries" . to_string ( ) ,
280+ ) ) ;
281+ }
282+
283+ if let Ok ( k) = key. extract :: < i64 > ( ) {
284+ Ok ( Key :: Int ( k) )
285+ } else if let Ok ( k) = key. extract :: < u64 > ( ) {
286+ Ok ( Key :: Uint ( k) )
287+ } else if let Ok ( k) = key. extract :: < bool > ( ) {
288+ Ok ( Key :: Bool ( k) )
289+ } else if let Ok ( k) = key. extract :: < String > ( ) {
290+ Ok ( Key :: String ( k. into ( ) ) )
291+ } else {
292+ Err ( CelError :: ConversionError (
293+ "Failed to convert Python mapping key to Key" . to_string ( ) ,
294+ ) )
295+ }
296+ }
297+
298+ fn mapping_to_value ( mapping : & Bound < ' _ , PyMapping > ) -> Result < Value , CelError > {
299+ let keys = mapping
300+ . keys ( )
301+ . map_err ( |e| CelError :: ConversionError ( format ! ( "Failed to read mapping keys: {e}" ) ) ) ?;
302+
303+ let mut map: HashMap < Key , Value > = HashMap :: new ( ) ;
304+ for key in keys. iter ( ) {
305+ let key_converted = Self :: key_from_py ( & key) ?;
306+ let value = mapping. get_item ( & key) . map_err ( |e| {
307+ CelError :: ConversionError ( format ! ( "Failed to read mapping item: {e}" ) )
308+ } ) ?;
309+ let value_converted = RustyPyType ( & value) . try_into_value ( ) . map_err ( |e| {
310+ CelError :: ConversionError ( format ! ( "Failed to convert mapping value: {e}" ) )
311+ } ) ?;
312+ map. insert ( key_converted, value_converted) ;
313+ }
314+
315+ Ok ( Value :: Map ( map. into ( ) ) )
316+ }
317+ }
318+
274319/// Build a CEL execution environment from an optional evaluation context.
275320///
276321/// This consolidates the shared logic used by `evaluate()` and `Program.execute()`
@@ -496,34 +541,32 @@ impl TryIntoValue for RustyPyType<'_> {
496541 . collect :: < Result < Vec < Value > , Self :: Error > > ( ) ;
497542 list. map ( |v| Value :: List ( Arc :: new ( v) ) )
498543 } else if let Ok ( value) = pyobject. cast :: < PyDict > ( ) {
499- let mut map: HashMap < Key , Value > = HashMap :: new ( ) ;
500- for ( key, value) in value. into_iter ( ) {
501- let key = if key. is_none ( ) {
502- return Err ( CelError :: ConversionError (
503- "None cannot be used as a key in dictionaries" . to_string ( ) ,
504- ) ) ;
505- } else if let Ok ( k) = key. extract :: < i64 > ( ) {
506- Key :: Int ( k)
507- } else if let Ok ( k) = key. extract :: < u64 > ( ) {
508- Key :: Uint ( k)
509- } else if let Ok ( k) = key. extract :: < bool > ( ) {
510- Key :: Bool ( k)
511- } else if let Ok ( k) = key. extract :: < String > ( ) {
512- Key :: String ( k. into ( ) )
513- } else {
514- return Err ( CelError :: ConversionError (
515- "Failed to convert PyDict key to Key" . to_string ( ) ,
516- ) ) ;
517- } ;
518- if let Ok ( dict_value) = RustyPyType ( & value) . try_into_value ( ) {
544+ let py = pyobject. py ( ) ;
545+ let is_exact_dict =
546+ pyobject. get_type ( ) . as_type_ptr ( ) == PyDict :: type_object ( py) . as_type_ptr ( ) ;
547+
548+ if is_exact_dict {
549+ let mut map: HashMap < Key , Value > = HashMap :: new ( ) ;
550+ for ( key, value) in value. into_iter ( ) {
551+ let key = Self :: key_from_py ( & key) ?;
552+ let dict_value = RustyPyType ( & value) . try_into_value ( ) . map_err ( |e| {
553+ CelError :: ConversionError ( format ! (
554+ "Failed to convert PyDict value to Value: {e}"
555+ ) )
556+ } ) ?;
519557 map. insert ( key, dict_value) ;
520- } else {
521- return Err ( CelError :: ConversionError (
522- "Failed to convert PyDict value to Value" . to_string ( ) ,
523- ) ) ;
524558 }
559+ Ok ( Value :: Map ( map. into ( ) ) )
560+ } else {
561+ let mapping = pyobject. cast :: < PyMapping > ( ) . map_err ( |e| {
562+ CelError :: ConversionError ( format ! (
563+ "Failed to cast dict subclass to mapping: {e}"
564+ ) )
565+ } ) ?;
566+ Self :: mapping_to_value ( mapping)
525567 }
526- Ok ( Value :: Map ( map. into ( ) ) )
568+ } else if let Ok ( mapping) = pyobject. cast :: < PyMapping > ( ) {
569+ Self :: mapping_to_value ( mapping)
527570 } else if let Ok ( value) = pyobject. extract :: < Vec < u8 > > ( ) {
528571 Ok ( Value :: Bytes ( value. into ( ) ) )
529572 } else {
0 commit comments