Source code for lb.types

# Copyright 2017 The Lambda-blocks developers. See AUTHORS for details.
#
# 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.

"""
Types manipulation.  This module defines functions to check for types
compatibility.
"""

from typing import Any, List, Mapping, Tuple, TupleMeta, TypeVar, CallableMeta

T = TypeVar('T')
ReturnType = Mapping[str, T]

[docs]def is_subtype(left, right): """ Checks if left is a subtype of right, i.e. if they are compatible. Currently implemented: basic types, Tuple, List, Callable. """ if left is Any or right is Any: # Any is compatible with everything, on both sides return True if isinstance(left, TupleMeta) and isinstance(right, TupleMeta): # in case of tuples on both sides, we check their items if hasattr(left, '__tuple_params__'): # Python 3.5.2 tuple_params = '__tuple_params__' else: # Python 3.5.3, 3.6 tuple_params = '__args__' left_items = getattr(left, tuple_params) right_items = getattr(right, tuple_params) if len(left_items) != len(right_items): return False return all([is_subtype(left_item, right_item) for (left_item, right_item) in zip(left_items, right_items)]) if isinstance(left, TupleMeta) != isinstance(right, TupleMeta): # tuple only on one side, not compatible return False def is_list_type(t): """ Hack to know if a type is typing.List[T] """ if hasattr(t, '__base__'): if t.__base__ in [list, List]: return True return False if is_list_type(left) and is_list_type(right): # extract the list type to avoid # TypeError: Parameterized generics cannot be used with class or instance checks return is_subtype(left.__args__[0], right.__args__[0]) if is_list_type(left) != is_list_type(right): if left == list or right == list: # one of them is a list but we don't know a list of what return True # List only on one side, not compatible return False def is_func_type(t): """ Callable, <func> """ try: if t.__name__ == 'function': return True except AttributeError: pass if type(t) == CallableMeta: return True return False if is_func_type(left) and is_func_type(right): return True # either a base type, or something else not supported return issubclass(left, right)
[docs]def is_sig_compatible(left, right): """ Checks if signature left is compatible with signature right, i.e. if the types in signature left can be fed to a function accepting the types in signature right. left and right are tuples of types (`tuple` as in Python built-in, not as in typing.Tuple) """ def to_tuple_type(tup): # sadly we can't do Tuple[*tup] if len(tup) > 5: raise Exception( "Due to Python's limitation regarding typing, it is currently " "not possible to have more than 5 inputs for a single block.") if len(tup) == 0: return type(None) # NoneType elif len(tup) == 1: return Tuple[tup[0]] elif len(tup) == 2: return Tuple[tup[0], tup[1]] elif len(tup) == 3: return Tuple[tup[0], tup[1], tup[2]] elif len(tup) == 4: return Tuple[tup[0], tup[1], tup[2], tup[3]] elif len(tup) == 5: return Tuple[tup[0], tup[1], tup[2], tup[3], tup[4]] return is_subtype(to_tuple_type(left), to_tuple_type(right))
[docs]def is_instance(var, type_): """ Checks if the type of a variable is compatible with another type. Currently it is only a wrapper around is_subtype, but we might add different type-checking later, e.g. with the types returned by the yaml parser. """ if hasattr(type_, '__origin__') and type_.__origin__ is not None: # this is to avoid "TypeError: Parameterized generics cannot # be used with class or instance checks", so we we don't # currently check the parameterized types type_ = type_.__origin__ return is_subtype(type(var), type_)
[docs]def type_of_mapping_values(type_): """ Given a Mapping type, returns the type of its values. """ assert is_subtype(type_, Mapping), \ 'Not a Mapping subtype: {}'.format(type_) return type_.__args__[0]