77 Generic ,
88 Generator ,
99 Optional ,
10- Coroutine ,
1110 AsyncContextManager ,
1211 Type ,
1312 cast ,
@@ -66,25 +65,25 @@ def __repr__(self) -> str:
6665 return f"{ self .__class__ .__name__ } ({ self .value !r} )"
6766
6867
69- class _FutureCachedValue (Generic [R , T ]):
70- """A placeholder object to control concurrent access to a cached awaitable value.
68+ class _FutureCachedPropertyValue (Generic [R , T ]):
69+ """
70+ A placeholder object to control concurrent access to a cached awaitable value
7171
7272 When given a lock to coordinate access, only the first task to await on a
7373 cached property triggers the underlying coroutine. Once a value has been
7474 produced, all tasks are unblocked and given the same, single value.
75-
7675 """
7776
78- __slots__ = ("_get_attribute " , "_instance" , "_name" , "_lock" )
77+ __slots__ = ("_func " , "_instance" , "_name" , "_lock" )
7978
8079 def __init__ (
8180 self ,
82- get_attribute : Callable [[T ], Coroutine [ Any , Any , R ]],
81+ func : Callable [[T ], Awaitable [ R ]],
8382 instance : T ,
8483 name : str ,
8584 lock : AsyncContextManager [Any ],
8685 ):
87- self ._get_attribute = get_attribute
86+ self ._func = func
8887 self ._instance = instance
8988 self ._name = name
9089 self ._lock = lock
@@ -98,7 +97,6 @@ def _instance_value(self) -> Awaitable[R]:
9897
9998 If the instance (no longer) has this attribute, it was deleted and the
10099 process is restarted by delegating to the descriptor.
101-
102100 """
103101 try :
104102 return self ._instance .__dict__ [self ._name ]
@@ -116,12 +114,17 @@ async def _await_impl(self) -> R:
116114 # the instance attribute is still this placeholder, and we
117115 # hold the lock. Start the getter to store the value on the
118116 # instance and return the value.
119- return await self ._get_attribute (self . _instance )
117+ return await self ._get_attribute ()
120118
121119 # another task produced a value, or the instance.__dict__ object was
122120 # deleted in the interim.
123121 return await stored
124122
123+ async def _get_attribute (self ) -> R :
124+ value = await self ._func (self ._instance )
125+ self ._instance .__dict__ [self ._name ] = AwaitableValue (value )
126+ return value
127+
125128 def __repr__ (self ) -> str :
126129 return (
127130 f"<{ type (self ).__name__ } for '{ type (self ._instance ).__name__ } ."
@@ -135,9 +138,10 @@ def __init__(
135138 getter : Callable [[T ], Awaitable [R ]],
136139 asynccontextmanager_type : Type [AsyncContextManager [Any ]] = nullcontext ,
137140 ):
138- self .func = getter
141+ self .func = self . __wrapped__ = getter
139142 self .attrname = None
140143 self .__doc__ = getter .__doc__
144+ self .__module__ = getter .__module__
141145 self ._asynccontextmanager_type = asynccontextmanager_type
142146
143147 def __set_name__ (self , owner : Any , name : str ) -> None :
@@ -175,19 +179,12 @@ def __get__(
175179 # on this instance. It takes care of coordinating between different
176180 # tasks awaiting on the placeholder until the cached value has been
177181 # produced.
178- wrapper = _FutureCachedValue (
179- self ._get_attribute , instance , name , self ._asynccontextmanager_type ()
182+ wrapper = _FutureCachedPropertyValue (
183+ self .func , instance , name , self ._asynccontextmanager_type ()
180184 )
181185 cache [name ] = wrapper
182186 return wrapper
183187
184- async def _get_attribute (self , instance : T ) -> R :
185- value = await self .func (instance )
186- name = self .attrname
187- assert name is not None # enforced in __get__
188- instance .__dict__ [name ] = AwaitableValue (value )
189- return value
190-
191188
192189def cached_property (
193190 type_or_getter : Union [Type [AsyncContextManager [Any ]], Callable [[T ], Awaitable [R ]]],
0 commit comments