"""Tests for C-implemented GenericAlias.""" import unittest import pickle import copy from collections import ( defaultdict, deque, OrderedDict, Counter, UserDict, UserList ) from collections.abc import * from concurrent.futures import Future from concurrent.futures.thread import _WorkItem from contextlib import AbstractContextManager, AbstractAsyncContextManager from contextvars import ContextVar, Token from csv import DictReader, DictWriter from dataclasses import Field from functools import partial, partialmethod, cached_property from graphlib import TopologicalSorter from logging import LoggerAdapter, StreamHandler from mailbox import Mailbox, _PartialFile try: import ctypes except ImportError: ctypes = None from difflib import SequenceMatcher from filecmp import dircmp from fileinput import FileInput from itertools import chain from http.cookies import Morsel try: from multiprocessing.managers import ValueProxy from multiprocessing.pool import ApplyResult from multiprocessing.queues import SimpleQueue as MPSimpleQueue except ImportError: # _multiprocessing module is optional ValueProxy = None ApplyResult = None MPSimpleQueue = None try: from multiprocessing.shared_memory import ShareableList except ImportError: # multiprocessing.shared_memory is not available on e.g. Android ShareableList = None from os import DirEntry from re import Pattern, Match from types import GenericAlias, MappingProxyType, AsyncGeneratorType from tempfile import TemporaryDirectory, SpooledTemporaryFile from urllib.parse import SplitResult, ParseResult from unittest.case import _AssertRaisesContext from queue import Queue, SimpleQueue from weakref import WeakSet, ReferenceType, ref import typing from typing import Unpack from typing import TypeVar T = TypeVar('T') K = TypeVar('K') V = TypeVar('V') _UNPACKED_TUPLES = [ # Unpacked tuple using `*` (*tuple[int],)[0], (*tuple[T],)[0], (*tuple[int, str],)[0], (*tuple[int, ...],)[0], (*tuple[T, ...],)[0], tuple[*tuple[int, ...]], tuple[*tuple[T, ...]], tuple[str, *tuple[int, ...]], tuple[*tuple[int, ...], str], tuple[float, *tuple[int, ...], str], tuple[*tuple[*tuple[int, ...]]], # Unpacked tuple using `Unpack` Unpack[tuple[int]], Unpack[tuple[T]], Unpack[tuple[int, str]], Unpack[tuple[int, ...]], Unpack[tuple[T, ...]], tuple[Unpack[tuple[int, ...]]], tuple[Unpack[tuple[T, ...]]], tuple[str, Unpack[tuple[int, ...]]], tuple[Unpack[tuple[int, ...]], str], tuple[float, Unpack[tuple[int, ...]], str], tuple[Unpack[tuple[Unpack[tuple[int, ...]]]]], # Unpacked tuple using `*` AND `Unpack` tuple[Unpack[tuple[*tuple[int, ...]]]], tuple[*tuple[Unpack[tuple[int, ...]]]], ] class BaseTest(unittest.TestCase): """Test basics.""" generic_types = [type, tuple, list, dict, set, frozenset, enumerate, defaultdict, deque, SequenceMatcher, dircmp, FileInput, OrderedDict, Counter, UserDict, UserList, Pattern, Match, partial, partialmethod, cached_property, TopologicalSorter, AbstractContextManager, AbstractAsyncContextManager, Awaitable, Coroutine, AsyncIterable, AsyncIterator, AsyncGenerator, Generator, Iterable, Iterator, Reversible, Container, Collection, Mailbox, _PartialFile, ContextVar, Token, Field, Set, MutableSet, Mapping, MutableMapping, MappingView, KeysView, ItemsView, ValuesView, Sequence, MutableSequence, MappingProxyType, AsyncGeneratorType, DirEntry, chain, LoggerAdapter, StreamHandler, TemporaryDirectory, SpooledTemporaryFile, Queue, SimpleQueue, _AssertRaisesContext, SplitResult, ParseResult, WeakSet, ReferenceType, ref, ShareableList, Future, _WorkItem, Morsel, DictReader, DictWriter] if ctypes is not None: generic_types.extend((ctypes.Array, ctypes.LibraryLoader)) if ValueProxy is not None: generic_types.extend((ValueProxy, ApplyResult, MPSimpleQueue)) def test_subscriptable(self): for t in self.generic_types: if t is None: continue tname = t.__name__ with self.subTest(f"Testing {tname}"): alias = t[int] self.assertIs(alias.__origin__, t) self.assertEqual(alias.__args__, (int,)) self.assertEqual(alias.__parameters__, ()) def test_unsubscriptable(self): for t in int, str, float, Sized, Hashable: tname = t.__name__ with self.subTest(f"Testing {tname}"): with self.assertRaisesRegex(TypeError, tname): t[int] def test_instantiate(self): for t in tuple, list, dict, set, frozenset, defaultdict, deque: tname = t.__name__ with self.subTest(f"Testing {tname}"): alias = t[int] self.assertEqual(alias(), t()) if t is dict: self.assertEqual(alias(iter([('a', 1), ('b', 2)])), dict(a=1, b=2)) self.assertEqual(alias(a=1, b=2), dict(a=1, b=2)) elif t is defaultdict: def default(): return 'value' a = alias(default) d = defaultdict(default) self.assertEqual(a['test'], d['test']) else: self.assertEqual(alias(iter((1, 2, 3))), t((1, 2, 3))) def test_unbound_methods(self): t = list[int] a = t() t.append(a, 'foo') self.assertEqual(a, ['foo']) x = t.__getitem__(a, 0) self.assertEqual(x, 'foo') self.assertEqual(t.__len__(a), 1) def test_subclassing(self): class C(list[int]): pass self.assertEqual(C.__bases__, (list,)) self.assertEqual(C.__class__, type) def test_class_methods(self): t = dict[int, None] self.assertEqual(dict.fromkeys(range(2)), {0: None, 1: None}) # This works self.assertEqual(t.fromkeys(range(2)), {0: None, 1: None}) # Should be equivalent def test_no_chaining(self): t = list[int] with self.assertRaises(TypeError): t[int] def test_generic_subclass(self): class MyList(list): pass t = MyList[int] self.assertIs(t.__origin__, MyList) self.assertEqual(t.__args__, (int,)) self.assertEqual(t.__parameters__, ()) def test_repr(self): class MyList(list): pass self.assertEqual(repr(list[str]), 'list[str]') self.assertEqual(repr(list[()]), 'list[()]') self.assertEqual(repr(tuple[int, ...]), 'tuple[int, ...]') x1 = tuple[ tuple( # Effectively the same as starring; TODO tuple[int] ) ] self.assertEqual(repr(x1), 'tuple[*tuple[int]]') x2 = tuple[ tuple( # Ditto TODO tuple[int, str] ) ] self.assertEqual(repr(x2), 'tuple[*tuple[int, str]]') x3 = tuple[ tuple( # Ditto TODO tuple[int, ...] ) ] self.assertEqual(repr(x3), 'tuple[*tuple[int, ...]]') self.assertTrue(repr(MyList[int]).endswith('.BaseTest.test_repr..MyList[int]')) self.assertEqual(repr(list[str]()), '[]') # instances should keep their normal repr def test_exposed_type(self): import types a = types.GenericAlias(list, int) self.assertEqual(str(a), 'list[int]') self.assertIs(a.__origin__, list) self.assertEqual(a.__args__, (int,)) self.assertEqual(a.__parameters__, ()) def test_parameters(self): from typing import List, Dict, Callable D0 = dict[str, int] self.assertEqual(D0.__args__, (str, int)) self.assertEqual(D0.__parameters__, ()) D1a = dict[str, V] self.assertEqual(D1a.__args__, (str, V)) self.assertEqual(D1a.__parameters__, (V,)) D1b = dict[K, int] self.assertEqual(D1b.__args__, (K, int)) self.assertEqual(D1b.__parameters__, (K,)) D2a = dict[K, V] self.assertEqual(D2a.__args__, (K, V)) self.assertEqual(D2a.__parameters__, (K, V)) D2b = dict[T, T] self.assertEqual(D2b.__args__, (T, T)) self.assertEqual(D2b.__parameters__, (T,)) L0 = list[str] self.assertEqual(L0.__args__, (str,)) self.assertEqual(L0.__parameters__, ()) L1 = list[T] self.assertEqual(L1.__args__, (T,)) self.assertEqual(L1.__parameters__, (T,)) L2 = list[list[T]] self.assertEqual(L2.__args__, (list[T],)) self.assertEqual(L2.__parameters__, (T,)) L3 = list[List[T]] self.assertEqual(L3.__args__, (List[T],)) self.assertEqual(L3.__parameters__, (T,)) L4a = list[Dict[K, V]] self.assertEqual(L4a.__args__, (Dict[K, V],)) self.assertEqual(L4a.__parameters__, (K, V)) L4b = list[Dict[T, int]] self.assertEqual(L4b.__args__, (Dict[T, int],)) self.assertEqual(L4b.__parameters__, (T,)) L5 = list[Callable[[K, V], K]] self.assertEqual(L5.__args__, (Callable[[K, V], K],)) self.assertEqual(L5.__parameters__, (K, V)) T1 = tuple[ tuple( # Ditto TODO tuple[int] ) ] self.assertEqual( T1.__args__, tuple( # Ditto TODO tuple[int] ) ) self.assertEqual(T1.__parameters__, ()) T2 = tuple[ tuple( # Ditto TODO tuple[T] ) ] self.assertEqual( T2.__args__, tuple( # Ditto TODO tuple[T] ) ) self.assertEqual(T2.__parameters__, (T,)) T4 = tuple[ tuple( # Ditto TODO tuple[int, str] ) ] self.assertEqual( T4.__args__, tuple( # Ditto TODO tuple[int, str] ) ) self.assertEqual(T4.__parameters__, ()) def test_parameter_chaining(self): from typing import List, Dict, Union, Callable self.assertEqual(list[T][int], list[int]) self.assertEqual(dict[str, T][int], dict[str, int]) self.assertEqual(dict[T, int][str], dict[str, int]) self.assertEqual(dict[K, V][str, int], dict[str, int]) self.assertEqual(dict[T, T][int], dict[int, int]) self.assertEqual(list[list[T]][int], list[list[int]]) self.assertEqual(list[dict[T, int]][str], list[dict[str, int]]) self.assertEqual(list[dict[str, T]][int], list[dict[str, int]]) self.assertEqual(list[dict[K, V]][str, int], list[dict[str, int]]) self.assertEqual(dict[T, list[int]][str], dict[str, list[int]]) self.assertEqual(list[List[T]][int], list[List[int]]) self.assertEqual(list[Dict[K, V]][str, int], list[Dict[str, int]]) self.assertEqual(list[Union[K, V]][str, int], list[Union[str, int]]) self.assertEqual(list[Callable[[K, V], K]][str, int], list[Callable[[str, int], str]]) self.assertEqual(dict[T, List[int]][str], dict[str, List[int]]) with self.assertRaises(TypeError): list[int][int] dict[T, int][str, int] dict[str, T][str, int] dict[T, T][str, int] def test_equality(self): self.assertEqual(list[int], list[int]) self.assertEqual(dict[str, int], dict[str, int]) self.assertEqual((*tuple[int],)[0], (*tuple[int],)[0]) self.assertEqual( tuple[ tuple( # Effectively the same as starring; TODO tuple[int] ) ], tuple[ tuple( # Ditto TODO tuple[int] ) ] ) self.assertNotEqual(dict[str, int], dict[str, str]) self.assertNotEqual(list, list[int]) self.assertNotEqual(list[int], list) self.assertNotEqual(list[int], tuple[int]) self.assertNotEqual((*tuple[int],)[0], tuple[int]) def test_isinstance(self): self.assertTrue(isinstance([], list)) with self.assertRaises(TypeError): isinstance([], list[str]) def test_issubclass(self): class L(list): ... self.assertTrue(issubclass(L, list)) with self.assertRaises(TypeError): issubclass(L, list[str]) def test_type_generic(self): t = type[int] Test = t('Test', (), {}) self.assertTrue(isinstance(Test, type)) test = Test() self.assertEqual(t(test), Test) self.assertEqual(t(0), int) def test_type_subclass_generic(self): class MyType(type): pass with self.assertRaisesRegex(TypeError, 'MyType'): MyType[int] def test_pickle(self): aliases = [GenericAlias(list, T)] + _UNPACKED_TUPLES for alias in aliases: for proto in range(pickle.HIGHEST_PROTOCOL + 1): with self.subTest(alias=alias, proto=proto): s = pickle.dumps(alias, proto) loaded = pickle.loads(s) self.assertEqual(loaded.__origin__, alias.__origin__) self.assertEqual(loaded.__args__, alias.__args__) self.assertEqual(loaded.__parameters__, alias.__parameters__) self.assertEqual(type(loaded), type(alias)) def test_copy(self): class X(list): def __copy__(self): return self def __deepcopy__(self, memo): return self aliases = [ GenericAlias(list, T), GenericAlias(deque, T), GenericAlias(X, T) ] + _UNPACKED_TUPLES for alias in aliases: with self.subTest(alias=alias): copied = copy.copy(alias) self.assertEqual(copied.__origin__, alias.__origin__) self.assertEqual(copied.__args__, alias.__args__) self.assertEqual(copied.__parameters__, alias.__parameters__) copied = copy.deepcopy(alias) self.assertEqual(copied.__origin__, alias.__origin__) self.assertEqual(copied.__args__, alias.__args__) self.assertEqual(copied.__parameters__, alias.__parameters__) def test_unpack(self): alias = tuple[str, ...] self.assertIs(alias.__unpacked__, False) unpacked = (*alias,)[0] self.assertIs(unpacked.__unpacked__, True) def test_union(self): a = typing.Union[list[int], list[str]] self.assertEqual(a.__args__, (list[int], list[str])) self.assertEqual(a.__parameters__, ()) def test_union_generic(self): a = typing.Union[list[T], tuple[T, ...]] self.assertEqual(a.__args__, (list[T], tuple[T, ...])) self.assertEqual(a.__parameters__, (T,)) def test_dir(self): dir_of_gen_alias = set(dir(list[int])) self.assertTrue(dir_of_gen_alias.issuperset(dir(list))) for generic_alias_property in ("__origin__", "__args__", "__parameters__"): self.assertIn(generic_alias_property, dir_of_gen_alias) def test_weakref(self): for t in self.generic_types: if t is None: continue tname = t.__name__ with self.subTest(f"Testing {tname}"): alias = t[int] self.assertEqual(ref(alias)(), alias) def test_no_kwargs(self): # bpo-42576 with self.assertRaises(TypeError): GenericAlias(bad=float) def test_subclassing_types_genericalias(self): class SubClass(GenericAlias): ... alias = SubClass(list, int) class Bad(GenericAlias): def __new__(cls, *args, **kwargs): super().__new__(cls, *args, **kwargs) self.assertEqual(alias, list[int]) with self.assertRaises(TypeError): Bad(list, int, bad=int) def test_iter_creates_starred_tuple(self): t = tuple[int, str] iter_t = iter(t) x = next(iter_t) self.assertEqual(repr(x), '*tuple[int, str]') def test_calling_next_twice_raises_stopiteration(self): t = tuple[int, str] iter_t = iter(t) next(iter_t) with self.assertRaises(StopIteration): next(iter_t) def test_del_iter(self): t = tuple[int, str] iter_x = iter(t) del iter_x class TypeIterationTests(unittest.TestCase): _UNITERABLE_TYPES = (list, tuple) def test_cannot_iterate(self): for test_type in self._UNITERABLE_TYPES: with self.subTest(type=test_type): expected_error_regex = "object is not iterable" with self.assertRaisesRegex(TypeError, expected_error_regex): iter(test_type) with self.assertRaisesRegex(TypeError, expected_error_regex): list(test_type) with self.assertRaisesRegex(TypeError, expected_error_regex): for _ in test_type: pass def test_is_not_instance_of_iterable(self): for type_to_test in self._UNITERABLE_TYPES: self.assertNotIsInstance(type_to_test, Iterable) if __name__ == "__main__": unittest.main()