@@ -24,9 +24,10 @@ def __init__(self, parent, strategy_key, fallback_value=None):
2424 def _load_for_state (self , state , passive ):
2525 """
2626 Override the default behavior to return fallback value instead of raising
27- DetachedInstanceError when session is None.
27+ DetachedInstanceError or MissingGreenlet when session is None or async context is missing .
2828 """
2929 from sqlalchemy .orm import LoaderCallableStatus
30+ from sqlalchemy .orm .attributes import set_committed_value
3031
3132 if not state .key :
3233 return LoaderCallableStatus .ATTR_EMPTY
@@ -37,13 +38,54 @@ def _load_for_state(self, state, passive):
3738 if not passive & PassiveFlag .SQL_OK :
3839 return LoaderCallableStatus .PASSIVE_NO_RESULT
3940
41+ # Check if the attribute is already loaded
42+ if self .key not in state .unloaded :
43+ # Attribute is already loaded, use parent implementation
44+ return super ()._load_for_state (state , passive )
45+
4046 # Check if we have a session before attempting to load
4147 session = _state_session (state )
4248 if session is None :
43- # No session available, return fallback value directly
49+ # No session available, set fallback value directly on the instance
50+ instance = state .obj ()
51+ if instance is not None :
52+ set_committed_value (instance , self .key , self .fallback_value )
53+ return LoaderCallableStatus .ATTR_WAS_SET
4454 return self .fallback_value
4555
46- # We have a session, use the parent implementation
56+ # Check if this is an async session context that might cause MissingGreenlet
57+ try :
58+ # Try to access session._connection_for_bind to check if we're in async context
59+ # without proper greenlet
60+ if hasattr (session , "get_bind" ) and hasattr (
61+ session , "_connection_for_bind"
62+ ):
63+ # This is a more elegant way to detect async context issues
64+ # If we're in async session without greenlet context, this will fail
65+ session .get_bind ()
66+ except Exception as e :
67+ # Handle async-related errors (MissingGreenlet, etc.)
68+ error_msg = str (e ).lower ()
69+ if any (
70+ keyword in error_msg
71+ for keyword in [
72+ "greenlet" ,
73+ "await_only" ,
74+ "asyncio" ,
75+ "async" ,
76+ "missinggreenlet" ,
77+ ]
78+ ):
79+ # This is an async-related error, set fallback value directly
80+ instance = state .obj ()
81+ if instance is not None :
82+ set_committed_value (instance , self .key , self .fallback_value )
83+ return LoaderCallableStatus .ATTR_WAS_SET
84+ return self .fallback_value
85+ # For other exceptions, re-raise them
86+ raise
87+
88+ # We have a proper session, use the parent implementation
4789 return super ()._load_for_state (state , passive )
4890
4991
0 commit comments