Plain is headed towards 1.0! Subscribe for development updates →

 1from __future__ import annotations
 2
 3import base64
 4import unicodedata
 5from binascii import Error as BinasciiError
 6
 7from plain.internal import internalcode
 8
 9
10@internalcode
11def urlsafe_base64_encode(s: bytes) -> str:
12    """
13    Encode a bytestring to a base64 string for use in URLs. Strip any trailing
14    equal signs.
15    """
16    return base64.urlsafe_b64encode(s).rstrip(b"\n=").decode("ascii")
17
18
19@internalcode
20def urlsafe_base64_decode(s: str) -> bytes:
21    """
22    Decode a base64 encoded string. Add back any trailing equal signs that
23    might have been stripped.
24    """
25    s_bytes = s.encode()
26    try:
27        return base64.urlsafe_b64decode(
28            s_bytes.ljust(len(s_bytes) + len(s_bytes) % 4, b"=")
29        )
30    except (LookupError, BinasciiError) as e:
31        raise ValueError(e)
32
33
34@internalcode
35def unicode_ci_compare(s1: str, s2: str) -> bool:
36    """
37    Perform case-insensitive comparison of two identifiers, using the
38    recommended algorithm from Unicode Technical Report 36, section
39    2.11.2(B)(2).
40    """
41    return (
42        unicodedata.normalize("NFKC", s1).casefold()
43        == unicodedata.normalize("NFKC", s2).casefold()
44    )