Coverage for bookbuilderpy/types.py: 91%
46 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-17 23:15 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-17 23:15 +0000
1"""Some basic type handling routines."""
2from typing import Any, Final, Iterable
5def type_name(tpe: type) -> str:
6 """
7 Convert a type to a string.
9 :param tpe: the type
10 :returns: the string
12 >>> type_name(type(None))
13 'None'
14 >>> type_name(int)
15 'int'
16 >>> from bookbuilderpy.path import Path
17 >>> type_name(Path)
18 'bookbuilderpy.path.Path'
19 """
20 c1: str = str(tpe)
21 if c1.startswith("<class '"):
22 c1 = c1[8:-2]
23 if c1 == "NoneType":
24 return "None"
26 if hasattr(tpe, "__qualname__"):
27 c2: str = tpe.__qualname__
28 if hasattr(tpe, "__module__"):
29 module = tpe.__module__
30 if (module is not None) and (module != "builtins"):
31 c2 = f"{module}.{c2}"
32 if len(c2) > len(c1):
33 return c2
34 return c1
37def type_name_of(obj) -> str:
38 """
39 Get the fully-qualified class name of an object.
41 :param obj: the object
42 :returns: the fully-qualified class name of the object
44 >>> from bookbuilderpy.path import Path
45 >>> type_name_of(Path.path("/tmp"))
46 'bookbuilderpy.path.Path'
47 """
48 if obj is None:
49 return "None"
50 c1: Final[str] = type_name(type(obj))
51 if hasattr(obj, "__class__"):
52 cls: Final[type] = obj.__class__
53 c2: str = type_name(cls)
55 if hasattr(cls, "__qualname__"):
56 c3: str = cls.__qualname__
57 if hasattr(obj, "__module__"):
58 module = obj.__module__
59 if (module is not None) and (module != "builtins"):
60 c3 = f"{module}.{c3}"
61 if len(c3) > len(c2):
62 c2 = c3
64 if len(c2) > len(c1):
65 return c2
66 return c1
69def type_error(obj: Any,
70 name: str,
71 expected: None | type | Iterable[type] = None,
72 call: bool = False) -> ValueError | TypeError:
73 """
74 Create an error to raise if a type did not fit.
76 :param obj: the object that is of the wrong type
77 :param name: the name of the object
78 :param expected: the expected types (or `None`)
79 :param call: the object should have been callable?
80 :returns: a :class:`TypeError` with a descriptive information
82 >>> type_error(1.3, "var", int)
83 TypeError("var should be an instance of int but is float, namely '1.3'.")
84 >>> type_error("x", "z", (int, float)).args[0]
85 "z should be an instance of any in {float, int} but is str, namely 'x'."
86 >>> type_error("f", "q", call=True).args[0]
87 "q should be a callable but is str, namely 'f'."
88 >>> type_error("1", "2", bool, call=True).args[0]
89 "2 should be an instance of bool or a callable but is str, namely '1'."
90 >>> type_error(None, "x", str)
91 TypeError('x should be an instance of str but is None.')
92 """
93 exp: str = ""
94 if isinstance(expected, Iterable):
95 exp = ", ".join(sorted([type_name(e) for e in expected]))
96 exp = f"an instance of any in {{{exp}}}"
97 elif expected is not None:
98 exp = f"an instance of {type_name(expected)}"
99 if call:
100 exp = f"{exp} or a callable" if exp else "a callable"
102 if obj is None:
103 return TypeError(f"{name} should be {exp} but is None.")
104 return TypeError(f"{name} should be {exp} but is "
105 f"{type_name_of(obj)}, namely '{obj}'.")