diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -91,10 +91,18 @@ # Non-generic type, check it directly if origin is None: - return type(value) == type_ + # This is functionally equivalent to using just this: + # return isinstance(value, type) + # but using type equality before isinstance allows very quick checks + # when the exact class is used (which is the overwhelming majority of cases) + # while still allowing subclasses to be used. + return type(value) == type_ or isinstance(value, type_) # Check the type of the value itself - if origin is not Union and type(value) != origin: + # + # For the same reason as above, this condition is functionally equivalent to: + # if origin is not Union and not isinstance(value, origin): + if origin is not Union and type(value) != origin and not isinstance(value, origin): return False # Then, if it's a container, check its items. diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py --- a/swh/model/tests/test_model.py +++ b/swh/model/tests/test_model.py @@ -3,6 +3,7 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +import collections import copy import datetime from typing import Any, List, Optional, Tuple, Union @@ -74,6 +75,23 @@ assert obj_as_dict == type(obj).from_dict(obj_as_dict).to_dict() +@attr.s +class Cls1: + pass + + +@attr.s +class Cls2(Cls1): + pass + + +_custom_namedtuple = collections.namedtuple("_custom_namedtuple", "a b") + + +class _custom_tuple(tuple): + pass + + # List of (type, valid_values, invalid_values) _TYPE_VALIDATOR_PARAMETERS: List[Tuple[Any, List[Any], List[Any]]] = [ # base types: @@ -84,8 +102,8 @@ ), ( int, - [-1, 0, 1, 42, 1000], - [True, False, None, "123", 0.0, (), ImmutableDict(), DentryPerms.directory], + [-1, 0, 1, 42, 1000, DentryPerms.directory, True, False], + [None, "123", 0.0, (), ImmutableDict()], ), ( float, @@ -101,8 +119,8 @@ # unions: ( Optional[int], - [None, -1, 0, 1, 42, 1000], - ["123", 0.0, (), ImmutableDict(), DentryPerms.directory], + [None, -1, 0, 1, 42, 1000, DentryPerms.directory], + ["123", 0.0, (), ImmutableDict()], ), ( Optional[bytes], @@ -122,12 +140,19 @@ # tuples ( Tuple[str, str], - [("foo", "bar"), ("", "")], + [("foo", "bar"), ("", ""), _custom_namedtuple("", ""), _custom_tuple(("", ""))], [("foo",), ("foo", "bar", "baz"), ("foo", 42), (42, "foo")], ), ( Tuple[str, ...], - [("foo",), ("foo", "bar"), ("", ""), ("foo", "bar", "baz")], + [ + ("foo",), + ("foo", "bar"), + ("", ""), + ("foo", "bar", "baz"), + _custom_namedtuple("", ""), + _custom_tuple(("", "")), + ], [("foo", 42), (42, "foo")], ), # composite generic: @@ -163,6 +188,7 @@ [ImmutableDict({"foo": "bar"}), ImmutableDict({42: 123})], ), # Any: + (object, [-1, 0, 1, 42, 1000, None, "123", 0.0, (), ImmutableDict()], [],), (Any, [-1, 0, 1, 42, 1000, None, "123", 0.0, (), ImmutableDict()], [],), ( ImmutableDict[Any, int], @@ -187,9 +213,10 @@ # attr objects: ( Timestamp, - [Timestamp(seconds=123, microseconds=0)], + [Timestamp(seconds=123, microseconds=0),], [None, "2021-09-28T11:27:59", 123], ), + (Cls1, [Cls1(), Cls2()], [None, b"abcd"],), # enums: ( TargetType, @@ -202,7 +229,7 @@ @pytest.mark.parametrize( "type_,value", [ - (type_, value) + pytest.param(type_, value, id=f"type={type_}, value={value}") for (type_, values, _) in _TYPE_VALIDATOR_PARAMETERS for value in values ], @@ -214,7 +241,7 @@ @pytest.mark.parametrize( "type_,value", [ - (type_, value) + pytest.param(type_, value, id=f"type={type_}, value={value}") for (type_, _, values) in _TYPE_VALIDATOR_PARAMETERS for value in values ],