Repository: twisted/twisted

Issue:
AttributeError: 'DelayedCall' object has no attribute 'creator'
|<img alt="allenap's avatar" src="https://avatars.githubusercontent.com/u/0?s=50" width="50" height="50">| allenap reported|
|-|-|
|Trac ID|trac#8306|
|Type|defect|
|Created|2016-04-26 16:04:05Z|

```
Python 3.5.1+ (default, Mar 30 2016, 22:46:26) 
[GCC 5.3.1 20160330] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from twisted.internet.base import DelayedCall
>>> dc = DelayedCall(1, lambda: None, (), {}, lambda dc: None, lambda dc: None)
>>> dc.debug = True
>>> str(dc)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../twisted/internet/base.py", line 206, in __str__
    L.append("\n\ntraceback at creation: \n\n%s" % ('    '.join(self.creator)))
AttributeError: 'DelayedCall' object has no attribute 'creator'
```

```
$ tail -n1 twisted/_version.py 
version = versions.Version('twisted', 16, 1, 1)
```


<details><summary>Searchable metadata</summary>

```
trac-id__8306 8306
type__defect defect
reporter__allenap allenap
priority__normal normal
milestone__None None
branch__ 
branch_author__ 
status__new new
resolution__None None
component__core core
keywords__None None
time__1461686645106304 1461686645106304
changetime__1462290530743285 1462290530743285
version__None None
owner__None None

```
</details>


Discussion:

Patch:
diff --git a/src/twisted/internet/base.py b/src/twisted/internet/base.py
index 1cd2c98ee4..4aab1c70d2 100644
--- a/src/twisted/internet/base.py
+++ b/src/twisted/internet/base.py
@@ -80,6 +80,9 @@ class DelayedCall:
     debug = False
     _repr: Optional[str] = None
 
+    # In debug mode, the call stack at the time of instantiation.
+    creator: Optional[Sequence[str]] = None
+
     def __init__(
         self,
         time: float,
@@ -265,7 +268,7 @@ class DelayedCall:
                 )
             L.append(")")
 
-        if self.debug:
+        if self.creator is not None:
             L.append("\n\ntraceback at creation: \n\n%s" % ("    ".join(self.creator)))
         L.append(">")
 
@@ -990,8 +993,8 @@ class ReactorBase(PluggableResolverMixin):
                 call.called = 1
                 call.func(*call.args, **call.kw)
             except BaseException:
-                log.deferr()
-                if hasattr(call, "creator"):
+                log.err()
+                if call.creator is not None:
                     e = "\n"
                     e += (
                         " C: previous exception occurred in "
diff --git a/src/twisted/newsfragments/8306.bugfix b/src/twisted/newsfragments/8306.bugfix
new file mode 100644
index 0000000000..455bc56485
--- /dev/null
+++ b/src/twisted/newsfragments/8306.bugfix
@@ -0,0 +1,1 @@
+``twisted.internet.base.DelayedCall.__repr__`` will no longer raise ``AttributeError`` if the ``DelayedCall`` was created before debug mode was enabled.  As a side-effect, ``twisted.internet.base.DelayedCall.creator`` is now defined as ``None`` in cases where previously it was undefined.


Test patch:
diff --git a/src/twisted/internet/test/test_base.py b/src/twisted/internet/test/test_base.py
index 3f8ecf08c2..31a32362e8 100644
--- a/src/twisted/internet/test/test_base.py
+++ b/src/twisted/internet/test/test_base.py
@@ -356,6 +356,17 @@ class DelayedCallNoDebugTests(DelayedCallMixin, TestCase):
         )
         self.assertEqual(str(dc), expected)
 
+    def test_switchToDebug(self):
+        """
+        If L{DelayedCall.debug} changes from C{0} to C{1} between
+        L{DelayeCall.__init__} and L{DelayedCall.__repr__} then
+        L{DelayedCall.__repr__} returns a string that does not include the
+        creator stack.
+        """
+        dc = DelayedCall(3, lambda: None, (), {}, nothing, nothing, lambda: 2)
+        dc.debug = 1
+        self.assertNotIn("traceback at creation", repr(dc))
+
 
 class DelayedCallDebugTests(DelayedCallMixin, TestCase):
     """
@@ -383,6 +394,17 @@ class DelayedCallDebugTests(DelayedCallMixin, TestCase):
         )
         self.assertRegex(str(dc), expectedRegexp)
 
+    def test_switchFromDebug(self):
+        """
+        If L{DelayedCall.debug} changes from C{1} to C{0} between
+        L{DelayeCall.__init__} and L{DelayedCall.__repr__} then
+        L{DelayedCall.__repr__} returns a string that includes the creator
+        stack (we captured it, we might as well display it).
+        """
+        dc = DelayedCall(3, lambda: None, (), {}, nothing, nothing, lambda: 2)
+        dc.debug = 0
+        self.assertIn("traceback at creation", repr(dc))
+
 
 class TestSpySignalCapturingReactor(ReactorBase):