Skip to content

Should decode() offer best-effort per-label decoding?Β #248

Closed

Description

@Kludex

idna.decode() accepts a full multi-label domain and decodes it (e.g. idna.decode("www.xn--galit-femmes-hommes-9ybf.gouv.fr") -> "www.Γ©galitΓ©-femmes-hommes.gouv.fr"), which is great. But it is all-or-nothing: if a single label is invalid, the whole call raises, even for the labels that are fine.

>>> idna.decode("a.b.c.xn--pokxncvks")
idna.core.InvalidCodepoint: Codepoint U+3253 at position 1 of 'γ‰“γ‹Žγ‹γ‹“γ‹•γ‹˜' not allowed

There seems to be no option to get a best-effort result (uts46=True, strict=False don't change this on 3.13).

This matters for "decode for display" use cases (HTTP clients exposing a Unicode host). The relevant standards specify per-label processing that keeps an invalid xn-- label unchanged rather than failing the whole domain:

Because decode() doesn't offer this, downstream projects need to re-implement the label-splitting themselves. Concretely, I got a PR in httpx2 (pydantic/httpx2#979) that implements the following:

labels = []
for label in host.split("."):
    if label.startswith("xn--"):
        try:
            label = idna.decode(label)
        except idna.IDNAError:
            pass
    labels.append(label)
host = ".".join(labels)

Since decode() already accepts a whole domain and owns label splitting internally, the "keep invalid label raw" behavior arguably belongs here (maybe a flag? e.g. ignore_invalid_punycode=True, mirroring UTS #46's IgnoreInvalidPunycode) rather than being re-implemented in every consumer.

Would the maintainers be open to such an option? Or is all-or-nothing the intended contract and per-label recovery deliberately left to callers?

AI Disclaimer

This issue was created with the assistance of Claude Code. I've edited accordingly.

Activity

  1. changed the title [-]Should decode() offer best-effort per-label decoding (keep invalid xn-- labels raw)?[/-] [+]Should decode() offer best-effort per-label decoding?[/+] on May 24, 2026
  2. changed the title [-]Should decode() offer best-effort per-label decoding?[/-] [+]Should `decode()` offer best-effort per-label decoding?[/+] on May 24, 2026
  3. kjd commented on May 24, 2026

    @kjd
    Owner

    Seems reasonable to consider. I think the major consideration is whether overloading the strict argument with this behavior is appropriate or whether another argument is warranted. There should be a protocol-conformant path available and should think about those relying on invoking the library to check for protocol conformance shouldn't be surprised.

  4. Kludex commented on May 24, 2026

    @Kludex
    Author

    What's the path forward? Should I bring a more concrete proposal?

  5. kjd commented on May 25, 2026

    @kjd
    Owner

    I have put together a potential implementation in the lenient-decode branch. I am not enamored with the API but it was the best I could think of so far.

  6. Kludex commented on May 26, 2026

    @Kludex
    Author

    I have put together a potential implementation in the lenient-decode branch. I am not enamored with the API but it was the best I could think of so far.

    What's wrong about it? The parameter name?

  7. kjd commented on May 27, 2026

    @kjd
    Owner

    I think my unease is that strict currently applies to one dimension of strictness, whereas lenient applies to a different dimension. I think ignore_invalid_punycode your proposed is too verbose, plus it is not really being ignored it is being passed through. Maybe something about the use case, i.e. for display purposes, is the angle to down here. While the functionality makes sense I don't have a clear view on how to present the interface.

  8. Kludex commented on Jun 1, 2026

    @Kludex
    Author

    It seems more maintainable to add a parameter, not sure what the name should be.

    On my side I have this PR that I want to act on: pydantic/httpx2#979 - but I don't want to merge it as is. Would it be okay if I vendor your approach on 47b5cde while you think about the best appraoch here?

  9. kjd commented on Jun 2, 2026

    @kjd
    Owner

    Renamed the argument to display and have merged it into master, will make a new release shortly with the functionality included.

  10. Kludex commented on Jun 2, 2026

    @Kludex
    Author

    Thanks! πŸ™

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions