{"record":{"id":"6db2a1c233cfe9aa","repo":"python-telegram-bot/python-telegram-bot","slug":"the-parameter-keyboard-should-be-a-sequence-of-s","errorCode":null,"errorMessage":"The parameter `keyboard` should be a sequence of sequences of strings or KeyboardButtons","messagePattern":"The parameter `keyboard` should be a sequence of sequences of strings or KeyboardButtons","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/telegram/_replykeyboardmarkup.py","lineNumber":146,"sourceCode":"        \"one_time_keyboard\",\n        \"resize_keyboard\",\n        \"selective\",\n    )\n\n    def __init__(\n        self,\n        keyboard: Sequence[Sequence[str | KeyboardButton]],\n        resize_keyboard: bool | None = None,\n        one_time_keyboard: bool | None = None,\n        selective: bool | None = None,\n        input_field_placeholder: str | None = None,\n        is_persistent: bool | None = None,\n        *,\n        api_kwargs: JSONDict | None = None,\n    ):\n        super().__init__(api_kwargs=api_kwargs)\n        if not check_keyboard_type(keyboard):\n            raise ValueError(\n                \"The parameter `keyboard` should be a sequence of sequences of \"\n                \"strings or KeyboardButtons\"\n            )\n\n        # Required\n        self.keyboard: tuple[tuple[KeyboardButton, ...], ...] = tuple(\n            tuple(KeyboardButton(button) if isinstance(button, str) else button for button in row)\n            for row in keyboard\n        )\n\n        # Optionals\n        self.resize_keyboard: bool | None = resize_keyboard\n        self.one_time_keyboard: bool | None = one_time_keyboard\n        self.selective: bool | None = selective\n        self.input_field_placeholder: str | None = input_field_placeholder\n        self.is_persistent: bool | None = is_persistent\n\n        self._id_attrs = (self.keyboard,)","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/python-telegram-bot/python-telegram-bot/blob/d3b69d2e9fb7af6c796f84516cfda751752c7cb7/src/telegram/_replykeyboardmarkup.py#L128-L164","documentation":"ReplyKeyboardMarkup rejected its keyboard argument because it is not a sequence of sequences of strings/KeyboardButtons. The constructor validates the shape eagerly so malformed keyboards fail at construction rather than at send time.","triggerScenarios":"Passing a flat list like ['A','B'] instead of [['A','B']], passing tuples of tuples with non-button items, a generator, or None to ReplyKeyboardMarkup(keyboard=...).","commonSituations":"Dynamically building keyboards from user data; refactoring from InlineKeyboardMarkup (which uses rows of dict/InlineKeyboardButton) to ReplyKeyboardMarkup.","solutions":["Wrap the row(s): ReplyKeyboardMarkup([['A','B']])","Ensure every inner element is a str or KeyboardButton","When building dynamically, use a list comprehension producing rows: [[KeyboardButton(b) for b in row] for row in buttons]"],"exampleFix":"# before\nkb = ReplyKeyboardMarkup(['Yes', 'No'])\n\n# after\nkb = ReplyKeyboardMarkup([['Yes', 'No']])","handlingStrategy":"type-guard","validationCode":"null","typeGuard":"from telegram import KeyboardButton\n\ndef is_valid_keyboard(kb) -> bool:\n    try:\n        return all(\n            isinstance(row, (list, tuple))\n            and all(isinstance(b, (str, KeyboardButton)) for b in row)\n            for row in kb\n        )\n    except TypeError:\n        return False","tryCatchPattern":"null","preventionTips":["Always nest rows one level deeper than the buttons","Construct rows with comprehensions: [[KeyboardButton(t) for t in row] for row in labels]]"],"tags":["telegram","reply-keyboard","validation","constructor"],"backgroundTag":"invalid-argument-shape","analyzedSha":"d3b69d2e9fb7af6c796f84516cfda751752c7cb7","analyzedAt":"2026-08-28T16:18:54.805Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}