{"record":{"id":"74b89054ab3611d9","repo":"roboflow/supervision","slug":"labels-is-a-dict-but-class-id-is-none-keypoints-m","errorCode":null,"errorMessage":"labels is a dict but class_id is None; KeyPoints must have class_id set.","messagePattern":"labels is a dict but class_id is None; KeyPoints must have class_id set\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/supervision/key_points/annotators.py","lineNumber":964,"sourceCode":"            center_y - text_h // 2,\n            center_x + text_w // 2,\n            center_y + text_h // 2,\n        )\n\n    @staticmethod\n    def _resolve_labels(\n        labels: list[str] | dict[int, list[str]] | None,\n        points_count: int,\n        class_id: int | None = None,\n    ) -> list[str]:\n        \"\"\"Return the label list for a single instance.\"\"\"\n        if labels is None:\n            return [str(j) for j in range(points_count)]\n\n        resolved: list[str]\n        if isinstance(labels, dict):\n            if class_id is None:\n                raise ValueError(\n                    \"labels is a dict but class_id is None; \"\n                    \"KeyPoints must have class_id set.\"\n                )\n            if class_id not in labels:\n                raise ValueError(f\"No labels defined for class_id={class_id}.\")\n            resolved = labels[class_id]\n        else:\n            resolved = labels\n\n        if len(resolved) != points_count:\n            raise ValueError(\n                f\"Number of labels ({len(resolved)}) must match \"\n                f\"number of key points ({points_count}).\"\n            )\n        return resolved\n\n    @staticmethod\n    def _resolve_color_list(","sourceCodeStart":946,"sourceCodeEnd":982,"githubUrl":"https://github.com/roboflow/supervision/blob/7f254d9784d4c37e0f03cd89ddee164c8db099c0/src/supervision/key_points/annotators.py#L946-L982","documentation":"Raised by verify_clean_wheel.py when a wheel's METADATA declares a Provides-Extra whose name contains 'opencv' (case-insensitive). Beyond dropping the runtime requirement, the packaging policy also forbids shipping an opencv-named extra at all — users should install opencv themselves. This check runs right after the Requires-Dist check on the same metadata.","triggerScenarios":"Declaring [project.optional-dependencies] opencv = [...] in pyproject.toml so the wheel advertises Provides-Extra: opencv; renaming an extra to something containing 'opencv' (e.g. 'opencv-headless'); building the wheel before removing the extra from packaging config.","commonSituations":"Contributors trying to make opencv installation convenient via an extra after the runtime dependency was removed; fork maintainers re-adding an opencv extra without updating the verification script's expectations; release CI failing on a branch that altered optional-dependency groups.","solutions":["Inspect the wheel's *.dist-info/METADATA Provides-Extra lines to find the offending extra name.","Remove the opencv extra from pyproject.toml's [project.optional-dependencies]; document manual opencv installation in the README instead.","If a variant install is genuinely needed, work with the maintainers to update the verification policy and packaging docs together rather than bypassing the check.","Rebuild and re-run .github/scripts/verify_clean_wheel.py to confirm a clean pass."],"exampleFix":"# before (pyproject.toml)\n[project.optional-dependencies]\nopencv = [\"opencv-python-headless\"]\n\n# after\n[project.optional-dependencies]\n# (no opencv extra; users install opencv separately)\ndev = [\"pytest\", \"ruff\"]","handlingStrategy":"validation","validationCode":"def wheel_has_no_opencv_extra(wheel: pathlib.Path) -> bool:\n    \"\"\"True if no Provides-Extra line mentions opencv.\"\"\"\n    with zipfile.ZipFile(wheel) as z:\n        meta = next(n for n in z.namelist() if n.endswith('/METADATA'))\n        text = z.read(meta).decode()\n    return not any(\n        line.startswith('Provides-Extra:') and 'opencv' in line.lower()\n        for line in text.splitlines()\n    )","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not add opencv-named extras to packaging metadata; document manual opencv installation instead.","Grep pyproject.toml for 'opencv' in optional-dependencies before opening a PR that touches packaging.","Keep packaging policy and the verify_clean_wheel checks in the same PR so they never drift apart."],"tags":["ci","packaging","wheel","opencv","extras","valueerror"],"backgroundTag":null,"analyzedSha":"7f254d9784d4c37e0f03cd89ddee164c8db099c0","analyzedAt":"2026-08-15T05:13:01.950Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}