@@ -497,6 +497,20 @@ def generate_getstate(cls: Type["Model"]) -> GetStateFn:
497497 return generate_function (source , namespace , "__getstate__" )
498498
499499
500+ class RestoreError (Exception ):
501+ """An exception raised when an error occurs while restoring a model from state."""
502+
503+ def __init__ (self , field : str , cls : Type ["Model" ], e : Exception ):
504+ self .field = field
505+ self .cls = cls
506+ self .exc = e
507+
508+ def __str__ (self ):
509+ return (
510+ f"Error restoring '{ self .field } ' on object of type { self .cls } : { self .exc } "
511+ )
512+
513+
500514def generate_restorestate (cls : Type ["Model" ]) -> RestoreStateFn :
501515 """Generate an optimized __restorestate__ function for the given model.
502516
@@ -548,6 +562,7 @@ def generate_restorestate(cls: Type["Model"]) -> RestoreStateFn:
548562
549563 namespace : DictType [str , Any ] = {
550564 "default_unflatten" : default_unflatten ,
565+ "RestoreError" : RestoreError ,
551566 }
552567 for order , f , m , unflatten in setters :
553568 # Since f is potentially an untrusted input, make sure it is a valid
@@ -569,7 +584,9 @@ def generate_restorestate(cls: Type["Model"]) -> RestoreStateFn:
569584 RelModel = types [0 ]
570585 if RelModel is not None :
571586 namespace [f"rel_model_{ f } " ] = RelModel
572- expr = f"await rel_model_{ f } .restore(state['{ f } '])"
587+ expr = (
588+ f"await rel_model_{ f } .restore(v) if (v := state['{ f } ']) else None"
589+ )
573590 elif is_primitive_member (m ):
574591 # Direct assignment
575592 expr = f"state['{ f } ']"
@@ -584,21 +601,20 @@ def generate_restorestate(cls: Type["Model"]) -> RestoreStateFn:
584601 expr = f"unflatten_{ f } (state['{ f } '], scope)"
585602
586603 # Do the assignment
587- if on_error == "raise" :
588- template .append (f" self.{ f } = { expr } " )
604+ if on_error == "ignore" :
605+ handler = "pass"
606+ elif on_error == "log" :
607+ handler = f"self.__log_restore_error__(e, '{ f } ', state, scope)"
589608 else :
590- if on_error == "log" :
591- handler = f"self.__log_restore_error__(e, '{ f } ', state, scope)"
592- else :
593- handler = "pass"
594- template .extend (
595- [
596- " try:" ,
597- f" self.{ f } = { expr } " ,
598- " except Exception as e:" ,
599- f" { handler } " ,
600- ]
601- )
609+ handler = f"raise RestoreError('{ f } ', self.__class__, e) from e"
610+ template .extend (
611+ [
612+ " try:" ,
613+ f" self.{ f } = { expr } " ,
614+ " except Exception as e:" ,
615+ f" { handler } " ,
616+ ]
617+ )
602618
603619 # Update restored state
604620 template .append ("self.__restored__ = True" )
@@ -778,6 +794,7 @@ def __log_restore_error__(
778794 @classmethod
779795 async def restore (cls : Type [M ], state : StateType , ** kwargs : Any ) -> M :
780796 """Restore an object from the database state"""
797+ assert state is not None
781798 obj = cls .__new__ (cls )
782799 await obj .__restorestate__ (state )
783800 return obj
@@ -801,6 +818,8 @@ def flatten(self, v: Any, scope: Optional[ScopeType] = None):
801818 a __py__ field and arguments to reconstruct it. Also see the coercers
802819
803820 """
821+ if v is None :
822+ return None
804823 if isinstance (v , (date , datetime , time )):
805824 # This is inefficient space wise but still allows queries
806825 s : DictType [str , Any ] = {
0 commit comments