Source code for descarteslabs.workflows.types.containers.tuple_

import operator

from collections import abc

from ....common.graft import client
from ...cereal import serializable
from ..core import (
    ProxyTypeError,
    GenericProxytype,
    typecheck_promote,
    assert_is_proxytype,
    merge_params,
)
from ..proxify import proxify
from ..primitives import Any, Int, Bool
from .slice import Slice

from ._check_valid_binop import check_valid_binop_for


[docs]@serializable() class Tuple(GenericProxytype): """ ``Tuple[item1_type, item2_type, ...]``: Proxy sequence of a fixed number of elements of specific types. A type must be specified for each item in the tuple. Can be instantiated from any Python iterable. When indexing with a modified proxy integer, the return type will be `Tuple[Any]` because the type of the value at the index is unknown (the index is unknown), but we know the length is 1. When slicing a Tuple with modified proxy integers, an `Any` will be returned because the types of the values at the indices is unknown, and the length of the resulting Tuple is unknown. Note: A modified proxy integer is a proxy integer that has been changed through other operations (`wf.Int(1) + 1` is a proxy Int that has been modified with an addition, `wf.Int(2)` has not been modified) Examples -------- >>> from descarteslabs.workflows import Tuple, Int, Float, Str, Bool >>> Tuple[Int, Float]([1, 2.2]) # 2-tuple of Int and Float <descarteslabs.workflows.types.containers.tuple_.Tuple[Int, Float] object at 0x...> >>> Tuple[Int]([1]) # 1-tuple of Int (NOT a variable length tuple of Ints) <descarteslabs.workflows.types.containers.tuple_.Tuple[Int] object at 0x...> >>> Tuple[Float, Float, Float]([1.1, 2.2, 3.3]) # 3-tuple of Floats <descarteslabs.workflows.types.containers.tuple_.Tuple[Float, Float, Float] object at 0x...> >>> Tuple[Str, Tuple[Int, Bool]](["foo", (1, True)]) # 2-tuple of Str and 2-tuple of Int and Bool <descarteslabs.workflows.types.containers.tuple_.Tuple[Str, Tuple[Int, Bool]] object at 0x...> >>> from descarteslabs.workflows import Tuple, Str >>> my_tuple = Tuple[Str, Str](["hello", "world"]) >>> my_tuple <descarteslabs.workflows.types.containers.tuple_.Tuple[Str, Str] object at 0x...> >>> my_tuple.compute() # doctest: +SKIP ('hello', 'world') >>> my_tuple[0].compute() # doctest: +SKIP 'hello' >>> from descarteslabs.workflows import Tuple, Int, Float, Str >>> tuple_a = Tuple[Int, Float]([1, 2.2]) >>> tuple_b = Tuple[Str, Float](["foo", 3.3]) >>> tuple_a + tuple_b <descarteslabs.workflows.types.containers.tuple_.Tuple[Int, Float, Str, Float] object at 0x...> >>> (tuple_a + ("x", False)).compute() # doctest: +SKIP (1, 2.2, "x", False) """ def __init__(self, iterable): value_types = self._type_params if value_types is None: raise TypeError( "Cannot instantiate a generic Tuple; the item types must be specified (like `Tuple[Int, Float]`)" ) # TODO: copy constructor if not isinstance(iterable, abc.Iterable): raise ProxyTypeError("Expected an iterable, got {}".format(iterable)) try: length = len(iterable) except TypeError: iterable = tuple(iterable) length = len(iterable) if length != len(self._type_params): raise ProxyTypeError( "To construct {}, expected an iterable of {} items, " "but got {} items".format( type(self).__name__, len(self._type_params), length ) ) def checker_promoter(i, x): cls = value_types[i] try: return cls._promote(x) except ProxyTypeError: raise ProxyTypeError( "While constructing {}, expected {} for tuple element {}, but got {!r}".format( type(self).__name__, cls, i, x ) ) iterable = tuple(checker_promoter(i, x) for i, x in enumerate(iterable)) self.graft = client.apply_graft("wf.tuple", *iterable) self.params = merge_params(*iterable) @classmethod def _validate_params(cls, type_params): for i, type_param in enumerate(type_params): error_message = "Tuple type parameters must be Proxytypes but for parameter {}, got {}".format( i, type_param ) assert_is_proxytype(type_param, error_message=error_message) @typecheck_promote((Int, Slice)) def __getitem__(self, item): # TODO(gabe): cache # TODO(gabe): no nested traceback on out-of-bounds index? type_slice = item.literal_value if isinstance(item, Int): item_type = ( self._type_params[type_slice] if isinstance(type_slice, int) else Any ) else: item_type = ( Any if type_slice is None else Tuple[self._type_params[type_slice]] ) return item_type._from_apply("wf.get", self, item) def __len__(self): try: return len(self._type_params) except TypeError: raise TypeError( "Generic Tuple has no length; must be parameterized with item types" )
[docs] def length(self): """Length is equivalent to the Python ``len`` operator. Returns ------- Int An Int Proxytype Example ------- >>> from descarteslabs.workflows import Tuple, Str >>> my_tuple = Tuple[Str, Str, Str](("foo", "bar", "baz")) >>> my_tuple.length().compute() # doctest: +SKIP 3 """ return Int._from_apply("wf.length", self)
def __iter__(self): for i in range(len(self)): yield self[i] @typecheck_promote(lambda self: type(self)) def __lt__(self, other): for elemtype in set(self._type_params): check_valid_binop_for( operator.lt, elemtype, "Operator `<` invalid for element {} in {}".format( elemtype.__name__, type(self).__name__ ), ) return Bool._from_apply("wf.lt", self, other) @typecheck_promote(lambda self: type(self)) def __le__(self, other): for elemtype in set(self._type_params): check_valid_binop_for( operator.le, elemtype, "Operator `<=` invalid for element {} in {}".format( elemtype.__name__, type(self).__name__ ), ) return Bool._from_apply("wf.le", self, other) @typecheck_promote(lambda self: type(self)) def __gt__(self, other): for elemtype in set(self._type_params): check_valid_binop_for( operator.gt, elemtype, "Operator `>` invalid for element {} in {}".format( elemtype.__name__, type(self).__name__ ), ) return Bool._from_apply("wf.gt", self, other) @typecheck_promote(lambda self: type(self)) def __ge__(self, other): for elemtype in set(self._type_params): check_valid_binop_for( operator.ge, elemtype, "Operator `>=` invalid for element {} in {}".format( elemtype.__name__, type(self).__name__ ), ) return Bool._from_apply("wf.ge", self, other) @typecheck_promote(lambda self: type(self)) def __eq__(self, other): for elemtype in set(self._type_params): check_valid_binop_for( operator.eq, elemtype, "Operator `==` invalid for element {} in {}".format( elemtype.__name__, type(self).__name__ ), ) return Bool._from_apply("wf.eq", self, other) @typecheck_promote(lambda self: type(self)) def __ne__(self, other): for elemtype in set(self._type_params): check_valid_binop_for( operator.ne, elemtype, "Operator `!=` invalid for element {} in {}".format( elemtype.__name__, type(self).__name__ ), ) return Bool._from_apply("wf.ne", self, other) def __add__(self, other): if isinstance(other, Tuple): concat_type = Tuple[self._type_params + other._type_params] elif isinstance(other, tuple): try: proxified = proxify(other) except NotImplementedError: return NotImplemented return self + proxified else: return NotImplemented return concat_type._from_apply("wf.add", self, other) def __radd__(self, other): if isinstance(other, Tuple): concat_type = Tuple[other._type_params + self._type_params] elif isinstance(other, tuple): try: proxified = proxify(other) except NotImplementedError: return NotImplemented return proxified + self else: return NotImplemented return concat_type._from_apply("wf.add", other, self)