|
10 | 10 | from test import support |
11 | 11 | import threading |
12 | 12 | import time |
| 13 | +import typing |
13 | 14 | import unittest |
14 | 15 | import unittest.mock |
15 | 16 | from weakref import proxy |
@@ -2119,6 +2120,73 @@ class X: |
2119 | 2120 | g._clear_cache() |
2120 | 2121 | self.assertEqual(len(td), 0) |
2121 | 2122 |
|
| 2123 | + def test_annotations(self): |
| 2124 | + @functools.singledispatch |
| 2125 | + def i(arg): |
| 2126 | + return "base" |
| 2127 | + @i.register |
| 2128 | + def _(arg: collections.abc.Mapping): |
| 2129 | + return "mapping" |
| 2130 | + @i.register |
| 2131 | + def _(arg: "collections.abc.Sequence"): |
| 2132 | + return "sequence" |
| 2133 | + self.assertEqual(i(None), "base") |
| 2134 | + self.assertEqual(i({"a": 1}), "mapping") |
| 2135 | + self.assertEqual(i([1, 2, 3]), "sequence") |
| 2136 | + self.assertEqual(i((1, 2, 3)), "sequence") |
| 2137 | + self.assertEqual(i("str"), "sequence") |
| 2138 | + |
| 2139 | + # Registering classes as callables doesn't work with annotations, |
| 2140 | + # you need to pass the type explicitly. |
| 2141 | + @i.register(str) |
| 2142 | + class _: |
| 2143 | + def __init__(self, arg): |
| 2144 | + self.arg = arg |
| 2145 | + |
| 2146 | + def __eq__(self, other): |
| 2147 | + return self.arg == other |
| 2148 | + self.assertEqual(i("str"), "str") |
| 2149 | + |
| 2150 | + def test_invalid_registrations(self): |
| 2151 | + msg_prefix = "Invalid first argument to `register()`: " |
| 2152 | + msg_suffix = ( |
| 2153 | + ". Use either `@register(some_class)` or plain `@register` on an " |
| 2154 | + "annotated function." |
| 2155 | + ) |
| 2156 | + @functools.singledispatch |
| 2157 | + def i(arg): |
| 2158 | + return "base" |
| 2159 | + with self.assertRaises(TypeError) as exc: |
| 2160 | + @i.register(42) |
| 2161 | + def _(arg): |
| 2162 | + return "I annotated with a non-type" |
| 2163 | + self.assertTrue(str(exc.exception).startswith(msg_prefix + "42")) |
| 2164 | + self.assertTrue(str(exc.exception).endswith(msg_suffix)) |
| 2165 | + with self.assertRaises(TypeError) as exc: |
| 2166 | + @i.register |
| 2167 | + def _(arg): |
| 2168 | + return "I forgot to annotate" |
| 2169 | + self.assertTrue(str(exc.exception).startswith(msg_prefix + |
| 2170 | + "<function TestSingleDispatch.test_invalid_registrations.<locals>._" |
| 2171 | + )) |
| 2172 | + self.assertTrue(str(exc.exception).endswith(msg_suffix)) |
| 2173 | + |
| 2174 | + # FIXME: The following will only work after PEP 560 is implemented. |
| 2175 | + return |
| 2176 | + |
| 2177 | + with self.assertRaises(TypeError) as exc: |
| 2178 | + @i.register |
| 2179 | + def _(arg: typing.Iterable[str]): |
| 2180 | + # At runtime, dispatching on generics is impossible. |
| 2181 | + # When registering implementations with singledispatch, avoid |
| 2182 | + # types from `typing`. Instead, annotate with regular types |
| 2183 | + # or ABCs. |
| 2184 | + return "I annotated with a generic collection" |
| 2185 | + self.assertTrue(str(exc.exception).startswith(msg_prefix + |
| 2186 | + "<function TestSingleDispatch.test_invalid_registrations.<locals>._" |
| 2187 | + )) |
| 2188 | + self.assertTrue(str(exc.exception).endswith(msg_suffix)) |
| 2189 | + |
2122 | 2190 |
|
2123 | 2191 | if __name__ == '__main__': |
2124 | 2192 | unittest.main() |
0 commit comments