# SPDX-License-Identifier: Apache-2.0
#
# Copyright (C) 2020, Arm Limited and contributors.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""
Generic types inspired by the :mod:`typing` module.
"""
import functools
import inspect
import typing
from typing import Any, Union, Generic, TypeVar
import typeguard
from collections.abc import Iterable
from lisa.utils import get_cls_name
class _TypeguardCustom:
_HINT = Any
@classmethod
def _instancecheck(cls, value):
return
@classmethod
def _typeguard_checker(cls, value, origin_type, args, memo):
typeguard.check_type_internal(value, cls._HINT, memo)
try:
cls._instancecheck(value)
except TypeError as e:
raise typeguard.TypeCheckError(str(e))
def _typeguard_lookup(origin_type, args, extras):
try:
issub = issubclass(origin_type, _TypeguardCustom)
except Exception:
issub = False
if issub:
return origin_type._typeguard_checker
else:
return None
typeguard.checker_lookup_functions.append(_typeguard_lookup)
[docs]
def check_type(x, classinfo):
"""
Equivalent of ``isinstance()`` that will also work with typing hints.
"""
if isinstance(classinfo, Iterable):
typ = Union[tuple(classinfo)]
else:
typ = classinfo
try:
typeguard.check_type(
value=x,
expected_type=typ,
forward_ref_policy=typeguard.ForwardRefPolicy.ERROR,
collection_check_strategy=typeguard.CollectionCheckStrategy.ALL_ITEMS,
)
except typeguard.TypeCheckError as e:
raise TypeError(str(e))
[docs]
def is_instance(obj, classinfo):
"""
Same as builtin ``isinstance()`` but works with type hints.
"""
try:
check_type(obj, classinfo)
except TypeError:
return False
else:
return True
[docs]
def is_hint(obj):
"""
Heuristic to check if a given ``obj`` is a typing hint or anything else.
This function will return ``False`` for classes.
.. warning:: Since there is currently no way to identify hints for sure,
the check might return ``False`` even if it is a hint.
"""
module = getattr(obj, '__module__', None)
# This is a class, so cannot be a hint.
if isinstance(obj, type):
return issubclass(obj, _TypeguardCustom)
elif module in ('typing', 'typing_extensions'):
return True
else:
return False
[docs]
@functools.lru_cache(maxsize=None, typed=True)
def hint_to_class(hint):
"""
Convert a typing hint to a class that will do a runtime check against the
hint when ``isinstance()`` is used.
"""
class Meta(type):
def __instancecheck__(cls, instance):
return is_instance(instance, hint)
class Stub(metaclass=Meta):
pass
name = get_cls_name(hint).split('.', 1)
try:
name = name[1]
except IndexError:
name = name[0]
Stub.__qualname__ = name
Stub.__name__ = name.split('.')[-1]
return Stub
T = TypeVar('T')
[docs]
class SortedSequence(Generic[T], _TypeguardCustom):
"""
Same as :class:`typing.List` but enforces sorted values when runtime
checked using :mod:`typeguard`.
"""
_HINT = typing.Sequence[T]
@classmethod
def _instancecheck(cls, value):
for i, (x, y) in enumerate(zip(value, value[1:])):
if x > y:
raise TypeError(f'Item #{i} "{x}" is higher than the next item "{y}", but the list must be sorted')