improve typing annotations in CachedCall (#10450)

tighten up some of the typing in CachedCall, which is going to be needed when
Twisted 21.7 brings better typing on Deferred.
This commit is contained in:
Richard van der Hoff 2021-07-28 12:25:12 +01:00 committed by GitHub
parent 752fe0cd98
commit 9643dfde6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 18 additions and 10 deletions

1
changelog.d/10450.misc Normal file
View File

@ -0,0 +1 @@
Update type annotations to work with forthcoming Twisted 21.7.0 release.

View File

@ -11,7 +11,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import enum
from typing import Awaitable, Callable, Generic, Optional, TypeVar, Union from typing import Awaitable, Callable, Generic, Optional, TypeVar, Union
from twisted.internet.defer import Deferred from twisted.internet.defer import Deferred
@ -22,6 +22,10 @@ from synapse.logging.context import make_deferred_yieldable, run_in_background
TV = TypeVar("TV") TV = TypeVar("TV")
class _Sentinel(enum.Enum):
sentinel = object()
class CachedCall(Generic[TV]): class CachedCall(Generic[TV]):
"""A wrapper for asynchronous calls whose results should be shared """A wrapper for asynchronous calls whose results should be shared
@ -65,7 +69,7 @@ class CachedCall(Generic[TV]):
""" """
self._callable: Optional[Callable[[], Awaitable[TV]]] = f self._callable: Optional[Callable[[], Awaitable[TV]]] = f
self._deferred: Optional[Deferred] = None self._deferred: Optional[Deferred] = None
self._result: Union[None, Failure, TV] = None self._result: Union[_Sentinel, TV, Failure] = _Sentinel.sentinel
async def get(self) -> TV: async def get(self) -> TV:
"""Kick off the call if necessary, and return the result""" """Kick off the call if necessary, and return the result"""
@ -78,8 +82,9 @@ class CachedCall(Generic[TV]):
self._callable = None self._callable = None
# once the deferred completes, store the result. We cannot simply leave the # once the deferred completes, store the result. We cannot simply leave the
# result in the deferred, since if it's a Failure, GCing the deferred # result in the deferred, since `awaiting` a deferred destroys its result.
# would then log a critical error about unhandled Failures. # (Also, if it's a Failure, GCing the deferred would log a critical error
# about unhandled Failures)
def got_result(r): def got_result(r):
self._result = r self._result = r
@ -92,13 +97,15 @@ class CachedCall(Generic[TV]):
# and any eventual exception may not be reported. # and any eventual exception may not be reported.
# we can now await the deferred, and once it completes, return the result. # we can now await the deferred, and once it completes, return the result.
if isinstance(self._result, _Sentinel):
await make_deferred_yieldable(self._deferred) await make_deferred_yieldable(self._deferred)
assert not isinstance(self._result, _Sentinel)
# I *think* this is the easiest way to correctly raise a Failure without having if isinstance(self._result, Failure):
# to gut-wrench into the implementation of Deferred. self._result.raiseException()
d = Deferred() raise AssertionError("unexpected return from Failure.raiseException")
d.callback(self._result)
return await d return self._result
class RetryOnExceptionCachedCall(Generic[TV]): class RetryOnExceptionCachedCall(Generic[TV]):