{"record":{"id":"a1769ea784f6d9c8","repo":"Textualize/textual","slug":"multiple-handlers-for-key-press-key-name-r-nwe","errorCode":null,"errorMessage":"Multiple handlers for key press {key_name!r}.\\nWe found both {first_handler!r} and {second_handler!r}, and didn't know which to call.\\nConsider combining them into a single handler.","messagePattern":"Multiple handlers for key press (.+?)\\.\\\\nWe found both (.+?) and (.+?), and didn't know which to call\\.\\\\nConsider combining them into a single handler\\.","errorType":"exception","errorClass":"DuplicateKeyHandlers","httpStatus":null,"severity":"error","filePath":"src/textual/_dispatch_key.py","lineNumber":43,"sourceCode":"    Raises:\n        DuplicateKeyHandlers: When there's more than 1 handler that could handle this key.\n    \"\"\"\n\n    def get_key_handler(pump: MessagePump, key: str) -> Callable | None:\n        \"\"\"Look for the public and private handler methods by name on self.\"\"\"\n        return getattr(pump, f\"key_{key}\", None) or getattr(pump, f\"_key_{key}\", None)\n\n    handled = False\n    invoked_method = None\n    key_name = event.name\n    if not key_name:\n        return False\n\n    def _raise_duplicate_key_handlers_error(\n        key_name: str, first_handler: str, second_handler: str\n    ) -> None:\n        \"\"\"Raise exception for case where user presses a key and there are multiple candidate key handler methods for it.\"\"\"\n        raise DuplicateKeyHandlers(\n            f\"Multiple handlers for key press {key_name!r}.\\n\"\n            f\"We found both {first_handler!r} and {second_handler!r}, \"\n            f\"and didn't know which to call.\\n\"\n            f\"Consider combining them into a single handler.\",\n        )\n\n    try:\n        screen = node.screen\n    except Exception:\n        screen = None\n    for key_method_name in event.name_aliases:\n        if (key_method := get_key_handler(node, key_method_name)) is not None:\n            if invoked_method:\n                _raise_duplicate_key_handlers_error(\n                    key_name, invoked_method.__name__, key_method.__name__\n                )\n            # If key handlers return False, then they are not considered handled\n            # This allows key handlers to do some conditional logic","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/_dispatch_key.py#L25-L61","documentation":"When a key is pressed, Textual's dispatch_key scans the widget for handler methods (key_<key>, key_<unicode>) and also checks the декорated @on(Key) style handlers via metadata. If it finds two candidate handlers for the same key, it refuses to guess and raises DuplicateKeyHandlers, suggesting you merge them.","triggerScenarios":"Defining both key_a(self) and an @on(Key('a')) handler on the same widget, or two decorated handlers matching the same key event, then pressing that key. Also occurs when a subclass inherits a key_x method and adds another handler for the same key.","commonSituations":"Copy-pasting a handler and changing only the decorator, migrating from key_ methods to @on decorators without removing the old method, or inheriting widgets whose base already defines a handler for the same key.","solutions":["Delete or rename one of the duplicate handlers so a single method handles the key","If migrating to @on decorators, remove the old key_<name> method","Move key handling to a parent/child level instead of duplicating on the same widget","Merge both handlers' logic into one method"],"exampleFix":"# before\ndef key_a(self) -> None:\n    self.add_note('a')\n\n@on(Key)\ndef key_handler(self, event: Key) -> None:\n    if event.key == 'a':\n        self.add_note('a')  # duplicate\n\n# after\n@on(Key)\ndef key_handler(self, event: Key) -> None:\n    if event.key == 'a':\n        self.add_note('a')","handlingStrategy":"validation","validationCode":"import inspect\n\ndef handler_names(widget, key: str) -> list[str]:\n    names = []\n    for name, member in inspect.getmembers(type(widget), inspect.isfunction):\n        if name == f'key_{key}':\n            names.append(name)\n        for meta in getattr(member, '__textual_on', []):\n            if getattr(meta[1] if isinstance(meta, tuple) else meta, 'key', None) == key:\n                names.append(name)\n    return names\n\n# assert len(handler_names(self, 'a')) <= 1","typeGuard":"def has_single_key_handler(widget, key: str) -> bool:\n    return len(handler_names(widget, key)) == 1","tryCatchPattern":"from textual._dispatch_key import DuplicateKeyHandlers\ntry:\n    dispatch_key(widget, key_event)\nexcept DuplicateKeyHandlers:\n    widget.notify('ambiguous key handling; check logs')","preventionTips":["Use either key_ methods or @on decorators for a given key, never both","When overriding key handlers in subclasses, check the base class first","Search your widget classes for duplicate key_<name> definitions during code review"],"tags":["python","textual","keyboard","event-handling","duplicate-handler"],"backgroundTag":"duplicate-event-handler","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}