Repository: twisted/twisted

Issue:
logging regression
As @om26er reports in https://github.com/twisted/twisted/pull/12040#issuecomment-1888181534 , the addition of parsing parens to match the documentation broke the subscripting part of the log-format protocol

Discussion:

Patch:
diff --git a/src/twisted/logger/_format.py b/src/twisted/logger/_format.py
index 959f4867ad..673ac977b2 100644
--- a/src/twisted/logger/_format.py
+++ b/src/twisted/logger/_format.py
@@ -201,6 +201,12 @@ class PotentialCallWrapper(object):
     def __getattr__(self, name: str) -> object:
         return keycall(name, self._wrapped.__getattribute__)
 
+    def __getitem__(self, name: str) -> object:
+        # The sub-object may not be indexable, but if it isn't, that's the
+        # caller's problem.
+        value = self._wrapped[name]  # type:ignore[index]
+        return PotentialCallWrapper(value)
+
     def __format__(self, format_spec: str) -> str:
         return self._wrapped.__format__(format_spec)
 


Test patch:
diff --git a/src/twisted/logger/test/test_format.py b/src/twisted/logger/test/test_format.py
index 6c830ca9f2..e16bcc6218 100644
--- a/src/twisted/logger/test/test_format.py
+++ b/src/twisted/logger/test/test_format.py
@@ -5,7 +5,7 @@
 Test cases for L{twisted.logger._format}.
 """
 
-from typing import AnyStr, Optional, cast
+from typing import AnyStr, Dict, Optional, cast
 
 try:
     from time import tzset
@@ -95,6 +95,22 @@ class FormattingTests(unittest.TestCase):
             "hello world", self.format("hello {what.where()}", what=World())
         )
 
+    def test_formatAttributeSubscript(self) -> None:
+        """
+        L{formatEvent} will format subscripts of attributes per PEP 3101.
+        """
+
+        class Example(object):
+            config: Dict[str, str] = dict(foo="bar", baz="qux")
+
+        self.assertEqual(
+            "bar qux",
+            self.format(
+                "{example.config[foo]} {example.config[baz]}",
+                example=Example(),
+            ),
+        )
+
     def test_formatEventNoFormat(self) -> None:
         """
         Formatting an event with no format.