{"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/3b7651241d4da534b3559b60ef128e1c34f54116/pandas/core/arrays/_arrow_string_mixins.py#L164-L200","documentation":"ValueError raised in _str_normalize of the ArrowStringArray mixin when the form argument is not one of the four Unicode normalization forms ('NFC', 'NFD', 'NFKC', 'NFKD'). The pyarrow backend validates the form before dispatching to pc.utf8_normalize or the unicodedata fallback.","triggerScenarios":"s.str.normalize('NFC ') (trailing space); s.str.normalize('nfc') (lowercase); s.str.normalize('XYZ') on a pyarrow-backed string Series.","commonSituations":"User-supplied normalization form strings from config or CLI args that don't exactly match the canonical uppercase names.","solutions":["Pass one of the exact uppercase forms: 'NFC', 'NFD', 'NFKC', 'NFKD'.","Normalize user input upstream: form = form.strip().upper() and validate against the four allowed values.","Provide a dropdown / enum in the UI so only valid forms are submitted."],"exampleFix":"# before\ns.str.normalize(user_form)  # user_form may be 'nfc'\n# after\nallowed = {'NFC','NFD','NFKC','NFKD'}\ns.str.normalize(user_form.strip().upper() if user_form.strip().upper() in allowed else 'NFC')","handlingStrategy":"validation","validationCode":"ALLOWED = {'NFC', 'NFD', 'NFKC', 'NFKD'}\ndef safe_normalize(s, form):\n    form = str(form).strip().upper()\n    if form not in ALLOWED:\n        raise ValueError(f'form must be one of {ALLOWED}; got {form!r}')\n    return s.str.normalize(form)","typeGuard":"def is_valid_normalization(form) -> bool:\n    return isinstance(form, str) and form.strip().upper() in {'NFC','NFD','NFKC','NFKD'}","tryCatchPattern":"try:\n    s.str.normalize(form)\nexcept ValueError as e:\n    if 'normalization form' in str(e):\n        s.str.normalize('NFC')  # safe default\n    else:\n        raise","preventionTips":["Constrain UI/config inputs for normalization form to the four canonical names.","Normalize user input with .strip().upper() and validate against an allowlist."],"tags":["strings","pyarrow","normalize","argument"],"backgroundTag":null,"analyzedSha":"3b7651241d4da534b3559b60ef128e1c34f54116","analyzedAt":"2026-08-11T22:10:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}