2023-03-09 19:50:11 +01:00
|
|
|
from typing import Generic, TypeVar
|
|
|
|
from unittest import TestCase
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
|
|
from marshmallow_generic import _util
|
|
|
|
|
|
|
|
|
|
|
|
class GenericInsightMixinTestCase(TestCase):
|
|
|
|
@patch.object(_util, "super")
|
|
|
|
def test___init_subclass__(self, mock_super: MagicMock) -> None:
|
|
|
|
mock_super_meth = MagicMock()
|
|
|
|
mock_super.return_value = MagicMock(__init_subclass__=mock_super_meth)
|
|
|
|
|
|
|
|
# Should be `None` by default:
|
2023-03-12 18:04:28 +01:00
|
|
|
self.assertIsNone(_util.GenericInsightMixin._type_arg_0) # type: ignore[misc]
|
|
|
|
self.assertIsNone(_util.GenericInsightMixin._type_arg_1) # type: ignore[misc]
|
|
|
|
self.assertIsNone(_util.GenericInsightMixin._type_arg_2) # type: ignore[misc]
|
|
|
|
self.assertIsNone(_util.GenericInsightMixin._type_arg_3) # type: ignore[misc]
|
|
|
|
self.assertIsNone(_util.GenericInsightMixin._type_arg_4) # type: ignore[misc]
|
2023-03-09 19:50:11 +01:00
|
|
|
|
|
|
|
# If the mixin type argument was not specified (still generic),
|
|
|
|
# ensure that the attribute remains `None` on the subclass:
|
|
|
|
t = TypeVar("t")
|
|
|
|
|
|
|
|
class Foo:
|
|
|
|
pass
|
|
|
|
|
|
|
|
class Bar(Generic[t]):
|
|
|
|
pass
|
|
|
|
|
2023-03-12 18:04:28 +01:00
|
|
|
class TestCls(Bar[str], _util.GenericInsightMixin[t, None, int, str, bool]):
|
2023-03-09 19:50:11 +01:00
|
|
|
pass
|
|
|
|
|
2023-03-12 18:04:28 +01:00
|
|
|
self.assertIsNone(TestCls._type_arg_0) # type: ignore[misc]
|
|
|
|
self.assertIsNone(TestCls._type_arg_1) # type: ignore[misc]
|
|
|
|
self.assertIs(int, TestCls._type_arg_2) # type: ignore[misc]
|
|
|
|
self.assertIs(str, TestCls._type_arg_3) # type: ignore[misc]
|
|
|
|
self.assertIs(bool, TestCls._type_arg_4) # type: ignore[misc]
|
2023-03-09 19:50:11 +01:00
|
|
|
mock_super.assert_called_once()
|
|
|
|
mock_super_meth.assert_called_once_with()
|
|
|
|
|
|
|
|
mock_super.reset_mock()
|
|
|
|
mock_super_meth.reset_mock()
|
|
|
|
|
2023-03-12 18:04:28 +01:00
|
|
|
# If the mixin type arguments were omitted,
|
|
|
|
# ensure the attributes remained `None`:
|
2023-03-09 19:50:11 +01:00
|
|
|
|
2023-03-12 18:04:28 +01:00
|
|
|
class UnspecifiedCls(_util.GenericInsightMixin): # type: ignore[type-arg]
|
2023-03-09 19:50:11 +01:00
|
|
|
pass
|
|
|
|
|
2023-03-12 18:04:28 +01:00
|
|
|
self.assertIsNone(UnspecifiedCls._type_arg_0) # type: ignore[misc]
|
|
|
|
self.assertIsNone(UnspecifiedCls._type_arg_1) # type: ignore[misc]
|
|
|
|
self.assertIsNone(UnspecifiedCls._type_arg_2) # type: ignore[misc]
|
|
|
|
self.assertIsNone(UnspecifiedCls._type_arg_3) # type: ignore[misc]
|
|
|
|
self.assertIsNone(UnspecifiedCls._type_arg_4) # type: ignore[misc]
|
2023-03-09 19:50:11 +01:00
|
|
|
mock_super.assert_called_once()
|
|
|
|
mock_super_meth.assert_called_once_with()
|
|
|
|
|
|
|
|
def test__get_type_arg(self) -> None:
|
|
|
|
with self.assertRaises(AttributeError):
|
2023-03-12 18:04:28 +01:00
|
|
|
_util.GenericInsightMixin._get_type_arg(0)
|
2023-03-09 19:50:11 +01:00
|
|
|
|
2023-03-12 18:04:28 +01:00
|
|
|
_type_0 = object()
|
|
|
|
_type_1 = object()
|
|
|
|
_type_2 = object()
|
|
|
|
_type_3 = object()
|
|
|
|
_type_4 = object()
|
|
|
|
with patch.multiple(
|
|
|
|
_util.GenericInsightMixin,
|
|
|
|
_type_arg_0=_type_0,
|
|
|
|
_type_arg_1=_type_1,
|
|
|
|
_type_arg_2=_type_2,
|
|
|
|
_type_arg_3=_type_3,
|
|
|
|
_type_arg_4=_type_4,
|
|
|
|
):
|
|
|
|
self.assertIs(_type_0, _util.GenericInsightMixin._get_type_arg(0))
|
|
|
|
self.assertIs(_type_1, _util.GenericInsightMixin._get_type_arg(1))
|
|
|
|
self.assertIs(_type_2, _util.GenericInsightMixin._get_type_arg(2))
|
|
|
|
self.assertIs(_type_3, _util.GenericInsightMixin._get_type_arg(3))
|
|
|
|
self.assertIs(_type_4, _util.GenericInsightMixin._get_type_arg(4))
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
_util.GenericInsightMixin._get_type_arg(5) # type: ignore[call-overload]
|