{"record":{"id":"7c47d6e2bcc405e8","repo":"pandas-dev/pandas","slug":"invalid-normalization-form","errorCode":null,"errorMessage":"invalid normalization form","messagePattern":"invalid normalization form","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/arrays/_arrow_string_mixins.py","lineNumber":182,"sourceCode":"                pa_pad = partial(pc.utf8_center, lean_left_on_odd_padding=lean_left)\n        else:\n            raise ValueError(\n                f\"Invalid side: {side}. Side must be one of 'left', 'right', 'both'\"\n            )\n        return self._from_pyarrow_array(\n            pa_pad(self._pa_array, width=width, padding=fillchar)\n        )\n\n    def _str_zfill(self, width: int) -> Self:\n        if pa_version_under21p0:\n            predicate = lambda val: val.zfill(width)\n            result = self._apply_elementwise(predicate)\n            return self._from_pyarrow_array(pa.chunked_array(result))\n        return self._from_pyarrow_array(pc.utf8_zfill(self._pa_array, width))\n\n    def _str_normalize(self, form: Literal[\"NFC\", \"NFD\", \"NFKC\", \"NFKD\"]) -> Self:\n        if form not in (\"NFC\", \"NFD\", \"NFKC\", \"NFKD\"):\n            raise ValueError(\"invalid normalization form\")\n        if form in (\"NFC\", \"NFKC\"):\n            # GH#64359 pc.utf8_normalize only decomposes; it skips the canonical\n            #  composition step, so for the composing forms it returns decomposed\n            #  output. Fall back to unicodedata for these.\n            predicate = lambda val: unicodedata.normalize(form, val)\n            result = self._apply_elementwise(predicate)\n            return self._from_pyarrow_array(pa.chunked_array(result))\n        return self._from_pyarrow_array(pc.utf8_normalize(self._pa_array, form=form))\n\n    def _str_get(self, i: int) -> Self:\n        lengths = pc.utf8_length(self._pa_array)\n        if i >= 0:\n            out_of_bounds = pc.greater_equal(i, lengths)\n            start = i\n            stop = i + 1\n            step = 1\n        else:\n            out_of_bounds = pc.greater(-i, lengths)","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/arrays/_arrow_string_mixins.py#L164-L200","documentation":"Raised by pyarrow-backed Series.str.normalize(form) when form is not one of the four Unicode normalization forms NFC, NFD, NFKC, NFKD. These are the only forms accepted by unicodedata.normalize and pyarrow's utf8_normalize. pandas validates the form before dispatching to the kernel.","triggerScenarios":"Calling s.str.normalize('nfc') (lowercase), 'NFKA' (typo), or any non-standard string on a string[pyarrow] Series.","commonSituations":"Case-sensitivity mistakes, typos in the form name, or form values read from external config without validation.","solutions":["Use exactly one of NFC, NFD, NFKC, NFKD (uppercase).","If form comes from user input, validate it against the 4-tuple before calling str.normalize."],"exampleFix":"// before\ns.str.normalize(\"nfc\")\n// after\ns.str.normalize(\"NFC\")","handlingStrategy":"validation","validationCode":"def safe_normalize(s, form=\"NFC\"):\n    if form not in (\"NFC\", \"NFD\", \"NFKC\", \"NFKD\"):\n        raise ValueError(f\"invalid normalization form {form!r}\")\n    return s.str.normalize(form)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate form against the 4-tuple (NFC, NFD, NFKC, NFKD) when it comes from config/user input"],"tags":["validation","string","pyarrow","unicode"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}