⏺ Summary of remaining Twisted defer usage:

- 0 @defer.inlineCallbacks — all converted to async def
  - 12 defer.ensureDeferred — reactor entry points (startup, shutdown, render)
  - 22 defer.Deferred() — in Linearizer, ReadWriteLock, AwakenableSleeper, DeferredEvent (old implementations)
  - 21 defer.gatherResults — in fallback paths and old implementations
  - 11 defer.succeed/fail — immediate value wrapping in old implementations
  - 3 defer.FirstError — in fallback paths
  - 13 defer.TimeoutError — in timeout_deferred and its callers

  The majority (22 + 21 + 11 + 13 = 67) are in the old Deferred-based utility implementations (Linearizer, ReadWriteLock, ObservableDeferred, timeout_deferred, etc.) that already have native replacements (NativeLinearizer,
  NativeReadWriteLock, ObservableFuture, native_timeout, etc.). These will be removed when callers switch to the native versions.

  The 12 defer.ensureDeferred are in reactor entry points that will be removed when reactor.run() → asyncio.run().

  The codebase is now in a clean transitional state where:
  1. All Twisted imports are conditional (try/except ImportError)
  2. ContextVar is the primary logcontext storage
  3. Test base class is stdlib (unittest.TestCase)
  4. CancelledError is asyncio.CancelledError in production code
  5. @defer.inlineCallbacks is eliminated (0 remaining)
  6. yieldable_gather_results uses asyncio.gather (with Twisted fallback)
  7. Module API is fully async (no more Deferred return types)
  8. Twisted is optional in pyproject.toml
This commit is contained in:
Matthew Hodgson
2026-03-21 20:54:28 +00:00
parent a5928e6839
commit 8e1c26067b
25 changed files with 160 additions and 184 deletions
@@ -25,7 +25,7 @@ from unittest import mock
try:
from twisted.enterprise.adbapi import ConnectionPool
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import Deferred, ensureDeferred
except ImportError:
pass
+1 -1
View File
@@ -26,7 +26,7 @@ import attr
try:
from twisted.internet import defer
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import Deferred
except ImportError:
pass
@@ -23,7 +23,7 @@ from typing import Collection
from unittest import mock
try:
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import ensureDeferred
except ImportError:
pass
+16 -4
View File
@@ -393,10 +393,22 @@ class TestCase(_stdlib_unittest.TestCase):
result = results[0]
if not isinstance(result, Failure):
self.fail(f"Deferred {d!r} succeeded with {result!r}, expected failure")
if expected_types and not result.check(*expected_types):
self.fail(
f"Expected {expected_types}, got {result.type}: {result.value}"
)
if expected_types:
# During transition, check both asyncio and Twisted CancelledError
expanded_types = list(expected_types)
from asyncio import CancelledError as AsyncCE
try:
from twisted.internet.defer import CancelledError as TwistedCE
if AsyncCE in expanded_types and TwistedCE not in expanded_types:
expanded_types.append(TwistedCE)
elif TwistedCE in expanded_types and AsyncCE not in expanded_types:
expanded_types.append(AsyncCE)
except ImportError:
pass
if not result.check(*expanded_types):
self.fail(
f"Expected {expected_types}, got {result.type}: {result.value}"
)
return result
def assertObjectHasAttributes(self, attrs: dict[str, object], obj: object) -> None:
+1 -1
View File
@@ -31,7 +31,7 @@ from unittest import mock
try:
from twisted.internet import defer, reactor
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IReactorTime
except ImportError:
+1 -1
View File
@@ -36,7 +36,7 @@ from synapse.util.duration import Duration
from tests.server import get_clock
from tests.unittest import TestCase
try:
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
except ImportError:
pass
+1 -1
View File
@@ -25,7 +25,7 @@ from parameterized import parameterized_class
try:
from twisted.internet import defer
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import Deferred, ensureDeferred
from twisted.python.failure import Failure
except ImportError:
+1 -1
View File
@@ -23,7 +23,7 @@ from typing import Hashable, Protocol
try:
from twisted.internet import defer
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import Deferred
except ImportError:
pass
+1 -1
View File
@@ -23,7 +23,7 @@ from typing import AsyncContextManager, Callable, Sequence
try:
from twisted.internet import defer
from twisted.internet.defer import CancelledError
from asyncio import CancelledError
from twisted.internet.defer import Deferred
except ImportError:
pass