{"record":{"id":"9d790e1e7c67af79","repo":"larksuite/cli","slug":"a1-range-must-include-a-cell-or-separator-ra","errorCode":null,"errorMessage":"A1 range must include a cell or ':' separator: {range_ref}","messagePattern":"A1 range must include a cell or ':' separator: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/lark-sheets/scripts/lark_sheet_range.py","lineNumber":99,"sourceCode":"    \"\"\"Parse the A1 range forms accepted by ``+csv-get``.\n\n    Open-ended forms need the caller's actual returned grid dimensions. This\n    keeps generated ranges finite without guessing a spreadsheet-wide limit.\n    \"\"\"\n    ref = range_ref.strip()\n    if \"!\" in ref:\n        _, ref = ref.rsplit(\"!\", 1)\n    if not ref:\n        raise ValueError(f\"Invalid A1 range: {range_ref}\")\n    parts = ref.split(\":\")\n    if len(parts) > 2:\n        raise ValueError(f\"Invalid A1 range: {range_ref}\")\n    start = _parse_endpoint(parts[0])\n    end = _parse_endpoint(parts[-1])\n\n    if len(parts) == 1:\n        if start[0] != \"cell\":\n            raise ValueError(f\"A1 range must include a cell or ':' separator: {range_ref}\")\n        _, row, col = start\n        return RangeBounds(row, col, row, col)\n\n    if start[0] == end[0] == \"cell\":\n        _, start_row, start_col = start\n        _, end_row, end_col = end\n    elif start[0] == end[0] == \"row\":\n        _, start_row = start\n        _, end_row = end\n        start_col = 1\n        if max_col is None:\n            raise ValueError(f\"Range needs a maximum column: {range_ref}\")\n        end_col = max_col\n    elif start[0] == end[0] == \"column\":\n        _, start_col = start\n        _, end_col = end\n        start_row = 1\n        if max_row is None:","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/skills/lark-sheets/scripts/lark_sheet_range.py#L81-L117","documentation":"When parse_range() receives a single endpoint (no ':'), that endpoint must be a cell; a bare row ('5') or column ('A') is only valid as one side of a 'start:end' pair. This error distinguishes a lone row/column endpoint, which has no defined bounds by itself, from a valid single-cell range.","triggerScenarios":"parse_range('5'), parse_range('A'), parse_range('$B'), parse_range('AA') — a single row or column endpoint without a ':' and second endpoint. Note 'A1:B' (cell:column) with max_row, or ':A1:B2' variants are handled elsewhere; this fires only for len(parts)==1 with start[0] != 'cell'.","commonSituations":"Passing just a column letter or row number when a whole-column/whole-row range was intended ('A' instead of 'A:A', '5' instead of '5:5'); config storing only the row/column dimension; dropping the second endpoint when programmatically building a range with an empty end value.","solutions":["For a whole column, pass 'A:A' (max_row must then be supplied by the caller); for a whole row, pass '5:5' (max_row→max_col semantics apply).","For a single cell, pass the full cell ref 'A1' rather than just the column 'A' or row '5'.","Check code that interpolates start/end — an empty end variable collapses 'A:A' to 'A'; default the end endpoint explicitly.","If you want one cell from row/col integers, build the ref with index_to_col(col) + str(row) before parsing."],"exampleFix":"// before\nparse_range(\"A\")    # A1 range must include a cell or ':' separator: A\nparse_range(\"5\")    # same error\n// after\nparse_range(\"A:A\")   # whole column (caller supplies max_row)\nparse_range(\"5:5\")   # whole row (caller supplies max_col)\nparse_range(\"A1\")    # single cell","handlingStrategy":"validation","validationCode":"import re\n_CELL = re.compile(r\"^\\$?[A-Za-z]+\\$?[1-9][0-9]*$\")\ndef standalone_endpoint_ok(ref: str) -> bool:\n    ref = ref.strip().rsplit(\"!\", 1)[-1]\n    if \":\" in ref:\n        return True   # paired endpoints are allowed to be row/column\n    return bool(_CELL.match(ref))  # lone endpoint must be a cell","typeGuard":"import re\n_CELL = re.compile(r\"^\\$?[A-Za-z]+\\$?[1-9][0-9]*$\")\ndef lone_cell(ref: str) -> str | None:\n    r = ref.strip().rsplit(\"!\", 1)[-1]\n    return r if \":\" not in r and _CELL.match(r) else None","tryCatchPattern":"from lark_sheet_range import parse_range\ntry:\n    bounds = parse_range(range_ref)\nexcept ValueError as e:\n    if \"must include a cell\" in str(e):\n        range_ref = f\"{range_ref}:{range_ref}\"  # 'A' -> 'A:A', '5' -> '5:5'\n        bounds = parse_range(range_ref)\n    else:\n        raise","preventionTips":["Whole column/row selections need both endpoints: 'A:A' or '5:5', never bare 'A' or '5'.","If building from row/col integers, always emit a full cell ref via index_to_col(col) + str(row).","Check for empty interpolated endpoints that silently reduce 'A:A' to 'A'.","Bare row/column endpoints also require max_col/max_row to be supplied to parse_range."],"tags":["python","input-validation","a1-notation","valueerror"],"backgroundTag":"invalid-range-notation","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}