{"record":{"id":"da741d623d7f922e","repo":"Textualize/rich","slug":"level-must-be-head-row-or-foot","errorCode":null,"errorMessage":"level must be 'head', 'row' or 'foot'","messagePattern":"level must be 'head', 'row' or 'foot'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"rich/box.py","lineNumber":150,"sourceCode":"            cross = self.head_row_cross\n            right = self.head_row_right\n        elif level == \"row\":\n            left = self.row_left\n            horizontal = self.row_horizontal\n            cross = self.row_cross\n            right = self.row_right\n        elif level == \"mid\":\n            left = self.mid_left\n            horizontal = \" \"\n            cross = self.mid_vertical\n            right = self.mid_right\n        elif level == \"foot\":\n            left = self.foot_row_left\n            horizontal = self.foot_row_horizontal\n            cross = self.foot_row_cross\n            right = self.foot_row_right\n        else:\n            raise ValueError(\"level must be 'head', 'row' or 'foot'\")\n\n        parts: List[str] = []\n        append = parts.append\n        if edge:\n            append(left)\n        for last, width in loop_last(widths):\n            append(horizontal * width)\n            if not last:\n                append(cross)\n        if edge:\n            append(right)\n        return \"\".join(parts)\n\n    def get_bottom(self, widths: Iterable[int]) -> str:\n        \"\"\"Get the bottom of a simple box.\n\n        Args:\n            widths (List[int]): Widths of columns.","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/Textualize/rich/blob/9d8f9a372cc5916fd4781fec207ced7ddac2f08f/rich/box.py#L132-L168","documentation":"Box.row (the method that renders one horizontal rule of a box character set) only accepts level values \"head\", \"row\", \"mid\", or \"foot\"; anything else raises ValueError(\"level must be 'head', 'row' or 'foot'\"). This is essentially an internal API used by Table rendering — the error message even forgets to mention the accepted \"mid\" level. End users normally never call it directly.","triggerScenarios":"Calling box.SQUARE.row(head_row, widths, level=...) with a misspelled or unknown level string; custom Table/box subclasses or plugins that invoke Box.row directly with their own level names; passing None or an empty string.","commonSituations":"Almost exclusively seen when writing custom table renderers or third-party extensions that reuse Box.row; occasionally triggered by copying example code that hardcodes an invalid level.","solutions":["Pass one of \"head\", \"row\", \"mid\", or \"foot\" as the level","If you don't need a semantic row type, use the default (typically \"row\")","Prefer composing boxes via the predefined box constants (box.SQUARE, box.DOUBLE, ...) rather than calling Box.row yourself"],"exampleFix":"// before (Python)\nsep = box.SQUARE.row([], widths, level=\"header\")  # ValueError\n// after\nsep = box.SQUARE.row([], widths, level=\"head\")","handlingStrategy":"validation","validationCode":"if level not in (\"head\", \"row\", \"mid\", \"foot\"):\n    raise ValueError(f\"invalid box level {level!r}\")\nline = box.SQUARE.row(characters, widths, level=level)","typeGuard":"from typing import Literal, TypeGuard\n\ndef is_box_level(v: object) -> TypeGuard[str]:\n    return v in (\"head\", \"row\", \"mid\", \"foot\")","tryCatchPattern":null,"preventionTips":["Treat Box.row as a semi-internal API; prefer the predefined box constants","Note the accepted set includes 'mid' even though the error message omits it","Centralize any direct Box.row calls in one wrapper you can test"],"tags":["python","rich","box","table","internal-api","valueerror"],"backgroundTag":null,"analyzedSha":"9d8f9a372cc5916fd4781fec207ced7ddac2f08f","analyzedAt":"2026-08-15T03:30:11.781Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}