1"""Internal utilities"""
2
3import base64
4import binascii
5from typing import Any
6
7
8def make_hashable(value: Any) -> Any:
9 """Return a hashable equivalent of a dict or list, for use in __hash__
10
11 Dicts become frozensets of their items and lists become tuples, both
12 recursively. Values that are already hashable are returned as they are, so
13 the result compares equal whenever the input does, which is what __hash__
14 needs.
15
16 Arguments:
17 value: Value to convert
18
19 Returns:
20 A hashable equivalent of the value
21 """
22
23 if isinstance(value, dict):
24 return frozenset((k, make_hashable(v)) for k, v in value.items())
25 if isinstance(value, (list, tuple)):
26 return tuple(make_hashable(v) for v in value)
27 return value
28
29
30def b64enc(data: bytes) -> str:
31 """To encode byte sequence into base64 string
32
33 Arguments:
34 data: Byte sequence to encode
35
36 Exceptions:
37 TypeError: If "data" is not byte sequence
38
39 Returns:
40 base64 string
41 """
42
43 return base64.standard_b64encode(data).decode("utf-8")
44
45
46def b64dec(string: str) -> bytes:
47 """To decode byte sequence from base64 string
48
49 Arguments:
50 string: base64 string to decode
51
52 Raises:
53 binascii.Error: If invalid base64-encoded string
54
55 Returns:
56 A byte sequence
57 """
58
59 data = string.encode("utf-8")
60 try:
61 return base64.b64decode(data, validate=True)
62 except binascii.Error:
63 # altchars for urlsafe encoded base64 - instead of + and _ instead of /
64 return base64.b64decode(data, altchars=b"-_", validate=True)