You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by bl...@apache.org on 2022/11/14 22:01:06 UTC
[iceberg] branch master updated: Python: Make invalid Literal conversions explicit (#6141)
This is an automated email from the ASF dual-hosted git repository.
blue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new 2d1e100be5 Python: Make invalid Literal conversions explicit (#6141)
2d1e100be5 is described below
commit 2d1e100be5199d9aabdf886bc7c324d353afd2d9
Author: Fokko Driesprong <fo...@apache.org>
AuthorDate: Mon Nov 14 23:00:58 2022 +0100
Python: Make invalid Literal conversions explicit (#6141)
Currently we silently turn Literals into None if we can't convert
them, instead I prefer to raise an exception. This can cause silent
bugs like: EqualTo(Reference("id"), StringLiteral("123a")) will turn
into a IsNull predicate since 123a cannot be casted to an Long
(assuming that the `id` column is a Long).
---
python/pyiceberg/expressions/literals.py | 264 ++++++++++++++++++------------
python/tests/expressions/test_literals.py | 134 ++++++++++-----
2 files changed, 248 insertions(+), 150 deletions(-)
diff --git a/python/pyiceberg/expressions/literals.py b/python/pyiceberg/expressions/literals.py
index d38cd5f741..81d92a4448 100644
--- a/python/pyiceberg/expressions/literals.py
+++ b/python/pyiceberg/expressions/literals.py
@@ -27,8 +27,9 @@ from datetime import date
from decimal import ROUND_HALF_UP, Decimal
from functools import singledispatch, singledispatchmethod
from typing import (
+ Any,
Generic,
- Optional,
+ Type,
TypeVar,
Union,
)
@@ -42,6 +43,7 @@ from pyiceberg.types import (
DoubleType,
FixedType,
FloatType,
+ IcebergType,
IntegerType,
LongType,
StringType,
@@ -66,49 +68,50 @@ T = TypeVar("T")
class Literal(Generic[T], ABC):
"""Literal which has a value and can be converted between types"""
- def __init__(self, value: T, value_type: type):
+ def __init__(self, value: T, value_type: Type):
if value is None or not isinstance(value, value_type):
raise TypeError(f"Invalid literal value: {value} (not a {value_type})")
self._value = value
@property
def value(self) -> T:
- return self._value # type: ignore
+ return self._value
+ @singledispatchmethod
@abstractmethod
- def to(self, type_var) -> Literal:
+ def to(self, type_var: IcebergType) -> Literal:
... # pragma: no cover
- def __repr__(self):
+ def __repr__(self) -> str:
return f"{type(self).__name__}({self.value})"
- def __str__(self):
+ def __str__(self) -> str:
return str(self.value)
- def __hash__(self):
+ def __hash__(self) -> int:
return hash(self.value)
- def __eq__(self, other):
+ def __eq__(self, other) -> bool:
return self.value == other.value
- def __ne__(self, other):
+ def __ne__(self, other) -> bool:
return not self.__eq__(other)
- def __lt__(self, other):
+ def __lt__(self, other) -> bool:
return self.value < other.value
- def __gt__(self, other):
+ def __gt__(self, other) -> bool:
return self.value > other.value
- def __le__(self, other):
+ def __le__(self, other) -> bool:
return self.value <= other.value
- def __ge__(self, other):
+ def __ge__(self, other) -> bool:
return self.value >= other.value
@singledispatch
-def literal(value) -> Literal:
+def literal(value: Any) -> Literal:
"""
A generic Literal factory to construct an iceberg Literal based on python primitive data type
using dynamic overloading
@@ -171,36 +174,60 @@ def _(value: date) -> Literal[int]:
return DateLiteral(date_to_days(value))
-class AboveMax(Singleton):
- @property
- def value(self):
- raise ValueError("AboveMax has no value")
+class FloatAboveMax(Literal[float], Singleton):
+ def __init__(self):
+ super().__init__(FloatType.max, float)
+
+ def to(self, type_var: IcebergType) -> Literal: # type: ignore
+ raise TypeError("Cannot change the type of FloatAboveMax")
+
+ def __repr__(self) -> str:
+ return "FloatAboveMax()"
- def to(self, type_var):
- raise TypeError("Cannot change the type of AboveMax")
+ def __str__(self) -> str:
+ return "FloatAboveMax"
- def __repr__(self):
- return "AboveMax()"
- def __str__(self):
- return "AboveMax"
+class FloatBelowMin(Literal[float], Singleton):
+ def __init__(self):
+ super().__init__(FloatType.min, float)
+
+ def to(self, type_var: IcebergType) -> Literal: # type: ignore
+ raise TypeError("Cannot change the type of FloatBelowMin")
+
+ def __repr__(self) -> str:
+ return "FloatBelowMin()"
+ def __str__(self) -> str:
+ return "FloatBelowMin"
-class BelowMin(Singleton):
+
+class IntAboveMax(Literal[int]):
def __init__(self):
- pass
+ super().__init__(IntegerType.max, int)
- def value(self):
- raise ValueError("BelowMin has no value")
+ def to(self, type_var: IcebergType) -> Literal: # type: ignore
+ raise TypeError("Cannot change the type of IntAboveMax")
- def to(self, type_var):
- raise TypeError("Cannot change the type of BelowMin")
+ def __repr__(self) -> str:
+ return "IntAboveMax()"
- def __repr__(self):
- return "BelowMin()"
+ def __str__(self) -> str:
+ return "IntAboveMax"
- def __str__(self):
- return "BelowMin"
+
+class IntBelowMin(Literal[int]):
+ def __init__(self):
+ super().__init__(IntegerType.min, int)
+
+ def to(self, type_var: IcebergType) -> Literal: # type: ignore
+ raise TypeError("Cannot change the type of IntBelowMin")
+
+ def __repr__(self) -> str:
+ return "IntBelowMin()"
+
+ def __str__(self) -> str:
+ return "IntBelowMin"
class BooleanLiteral(Literal[bool]):
@@ -208,11 +235,11 @@ class BooleanLiteral(Literal[bool]):
super().__init__(value, bool)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert BooleanLiteral into {type_var}")
@to.register(BooleanType)
- def _(self, type_var):
+ def _(self, _: BooleanType) -> Literal[bool]:
return self
@@ -221,39 +248,39 @@ class LongLiteral(Literal[int]):
super().__init__(value, int)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert LongLiteral into {type_var}")
@to.register(LongType)
- def _(self, type_var: LongType) -> Literal[int]:
+ def _(self, _: LongType) -> Literal[int]:
return self
@to.register(IntegerType)
- def _(self, _: IntegerType) -> Union[AboveMax, BelowMin, Literal[int]]:
+ def _(self, _: IntegerType) -> Literal[int]:
if IntegerType.max < self.value:
- return AboveMax()
+ return IntAboveMax()
elif IntegerType.min > self.value:
- return BelowMin()
+ return IntBelowMin()
return self
@to.register(FloatType)
- def _(self, type_var: FloatType) -> Literal[float]:
+ def _(self, _: FloatType) -> Literal[float]:
return FloatLiteral(float(self.value))
@to.register(DoubleType)
- def _(self, type_var: DoubleType) -> Literal[float]:
+ def _(self, _: DoubleType) -> Literal[float]:
return DoubleLiteral(float(self.value))
@to.register(DateType)
- def _(self, type_var: DateType) -> Literal[int]:
+ def _(self, _: DateType) -> Literal[int]:
return DateLiteral(self.value)
@to.register(TimeType)
- def _(self, type_var: TimeType) -> Literal[int]:
+ def _(self, _: TimeType) -> Literal[int]:
return TimeLiteral(self.value)
@to.register(TimestampType)
- def _(self, type_var: TimestampType) -> Literal[int]:
+ def _(self, _: TimestampType) -> Literal[int]:
return TimestampLiteral(self.value)
@to.register(DecimalType)
@@ -272,31 +299,31 @@ class FloatLiteral(Literal[float]):
super().__init__(value, float)
self._value32 = struct.unpack("<f", struct.pack("<f", value))[0]
- def __eq__(self, other):
+ def __eq__(self, other) -> bool:
return self._value32 == other
- def __lt__(self, other):
+ def __lt__(self, other) -> bool:
return self._value32 < other
- def __gt__(self, other):
+ def __gt__(self, other) -> bool:
return self._value32 > other
- def __le__(self, other):
+ def __le__(self, other) -> bool:
return self._value32 <= other
- def __ge__(self, other):
+ def __ge__(self, other) -> bool:
return self._value32 >= other
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert FloatLiteral into {type_var}")
@to.register(FloatType)
- def _(self, type_var: FloatType) -> Literal[float]:
+ def _(self, _: FloatType) -> Literal[float]:
return self
@to.register(DoubleType)
- def _(self, type_var: DoubleType) -> Literal[float]:
+ def _(self, _: DoubleType) -> Literal[float]:
return DoubleLiteral(self.value)
@to.register(DecimalType)
@@ -309,19 +336,19 @@ class DoubleLiteral(Literal[float]):
super().__init__(value, float)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert DoubleLiteral into {type_var}")
@to.register(DoubleType)
- def _(self, type_var: DoubleType) -> Literal[float]:
+ def _(self, _: DoubleType) -> Literal[float]:
return self
@to.register(FloatType)
- def _(self, _: FloatType) -> Union[AboveMax, BelowMin, Literal[float]]:
+ def _(self, _: FloatType) -> Union[FloatAboveMax, FloatBelowMin, FloatLiteral]:
if FloatType.max < self.value:
- return AboveMax()
+ return FloatAboveMax()
elif FloatType.min > self.value:
- return BelowMin()
+ return FloatBelowMin()
return FloatLiteral(self.value)
@to.register(DecimalType)
@@ -334,11 +361,11 @@ class DateLiteral(Literal[int]):
super().__init__(value, int)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert DateLiteral into {type_var}")
@to.register(DateType)
- def _(self, type_var: DateType) -> Literal[int]:
+ def _(self, _: DateType) -> Literal[int]:
return self
@@ -347,11 +374,11 @@ class TimeLiteral(Literal[int]):
super().__init__(value, int)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert TimeLiteral into {type_var}")
@to.register(TimeType)
- def _(self, type_var: TimeType) -> Literal[int]:
+ def _(self, _: TimeType) -> Literal[int]:
return self
@@ -360,15 +387,15 @@ class TimestampLiteral(Literal[int]):
super().__init__(value, int)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert TimestampLiteral into {type_var}")
@to.register(TimestampType)
- def _(self, type_var: TimestampType) -> Literal[int]:
+ def _(self, _: TimestampType) -> Literal[int]:
return self
@to.register(DateType)
- def _(self, type_var: DateType) -> Literal[int]:
+ def _(self, _: DateType) -> Literal[int]:
return DateLiteral(micros_to_days(self.value))
@@ -377,14 +404,14 @@ class DecimalLiteral(Literal[Decimal]):
super().__init__(value, Decimal)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert DecimalLiteral into {type_var}")
@to.register(DecimalType)
- def _(self, type_var: DecimalType) -> Optional[Literal[Decimal]]:
+ def _(self, type_var: DecimalType) -> Literal[Decimal]:
if type_var.scale == abs(self.value.as_tuple().exponent):
return self
- return None
+ raise ValueError(f"Could not convert {self.value} into a {type_var}")
class StringLiteral(Literal[str]):
@@ -392,52 +419,69 @@ class StringLiteral(Literal[str]):
super().__init__(value, str)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert StringLiteral into {type_var}")
@to.register(StringType)
- def _(self, type_var: StringType) -> Literal[str]:
+ def _(self, _: StringType) -> Literal[str]:
return self
+ @to.register(IntegerType)
+ def _(self, type_var: IntegerType) -> Union[IntAboveMax, IntBelowMin, LongLiteral]:
+ try:
+ number = int(float(self.value))
+
+ if IntegerType.max < number:
+ return IntAboveMax()
+ elif IntegerType.min > number:
+ return IntBelowMin()
+ return LongLiteral(number)
+ except ValueError as e:
+ raise ValueError(f"Could not convert {self.value} into a {type_var}") from e
+
+ @to.register(LongType)
+ def _(self, type_var: LongType) -> Literal[int]:
+ try:
+ return LongLiteral(int(float(self.value)))
+ except (TypeError, ValueError) as e:
+ raise ValueError(f"Could not convert {self.value} into a {type_var}") from e
+
@to.register(DateType)
- def _(self, type_var: DateType) -> Optional[Literal[int]]:
+ def _(self, type_var: DateType) -> Literal[int]:
try:
return DateLiteral(date_str_to_days(self.value))
- except (TypeError, ValueError):
- return None
+ except (TypeError, ValueError) as e:
+ raise ValueError(f"Could not convert {self.value} into a {type_var}") from e
@to.register(TimeType)
- def _(self, type_var: TimeType) -> Optional[Literal[int]]:
+ def _(self, type_var: TimeType) -> Literal[int]:
try:
return TimeLiteral(time_to_micros(self.value))
- except (TypeError, ValueError):
- return None
+ except (TypeError, ValueError) as e:
+ raise ValueError(f"Could not convert {self.value} into a {type_var}") from e
@to.register(TimestampType)
- def _(self, type_var: TimestampType) -> Optional[Literal[int]]:
+ def _(self, type_var: TimestampType) -> Literal[int]:
try:
return TimestampLiteral(timestamp_to_micros(self.value))
- except (TypeError, ValueError):
- return None
+ except (TypeError, ValueError) as e:
+ raise ValueError(f"Could not convert {self.value} into a {type_var}") from e
@to.register(TimestamptzType)
- def _(self, type_var: TimestamptzType) -> Optional[Literal[int]]:
- try:
- return TimestampLiteral(timestamptz_to_micros(self.value))
- except (TypeError, ValueError):
- return None
+ def _(self, _: TimestamptzType) -> Literal[int]:
+ return TimestampLiteral(timestamptz_to_micros(self.value))
@to.register(UUIDType)
- def _(self, type_var: UUIDType) -> Literal[UUID]:
+ def _(self, _: UUIDType) -> Literal[UUID]:
return UUIDLiteral(UUID(self.value))
@to.register(DecimalType)
- def _(self, type_var: DecimalType) -> Optional[Literal[Decimal]]:
+ def _(self, type_var: DecimalType) -> Literal[Decimal]:
dec = Decimal(self.value)
if type_var.scale == abs(dec.as_tuple().exponent):
return DecimalLiteral(dec)
else:
- return None
+ raise ValueError(f"Could not convert {self.value} into a {type_var}")
class UUIDLiteral(Literal[UUID]):
@@ -445,11 +489,11 @@ class UUIDLiteral(Literal[UUID]):
super().__init__(value, UUID)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert UUIDLiteral into {type_var}")
@to.register(UUIDType)
- def _(self, type_var: UUIDType) -> Literal[UUID]:
+ def _(self, _: UUIDType) -> Literal[UUID]:
return self
@@ -458,18 +502,18 @@ class FixedLiteral(Literal[bytes]):
super().__init__(value, bytes)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert FixedLiteral into {type_var}")
@to.register(FixedType)
- def _(self, type_var: FixedType) -> Optional[Literal[bytes]]:
+ def _(self, type_var: FixedType) -> Literal[bytes]:
if len(self.value) == len(type_var):
return self
else:
- return None
+ raise ValueError(f"Could not convert {self.value!r} into a {type_var}")
@to.register(BinaryType)
- def _(self, type_var: BinaryType) -> Literal[bytes]:
+ def _(self, _: BinaryType) -> Literal[bytes]:
return BinaryLiteral(self.value)
@@ -478,16 +522,18 @@ class BinaryLiteral(Literal[bytes]):
super().__init__(value, bytes)
@singledispatchmethod
- def to(self, type_var):
- return None
+ def to(self, type_var: IcebergType) -> Literal:
+ raise TypeError(f"Cannot convert BinaryLiteral into {type_var}")
@to.register(BinaryType)
def _(self, _: BinaryType) -> Literal[bytes]:
return self
@to.register(FixedType)
- def _(self, type_var: FixedType) -> Optional[Literal[bytes]]:
+ def _(self, type_var: FixedType) -> Literal[bytes]:
if len(type_var) == len(self.value):
return FixedLiteral(self.value)
else:
- return None
+ raise TypeError(
+ f"Cannot convert BinaryLiteral into {type_var}, different length: {len(type_var)} <> {len(self.value)}"
+ )
diff --git a/python/tests/expressions/test_literals.py b/python/tests/expressions/test_literals.py
index f34c8ae683..dee19359e6 100644
--- a/python/tests/expressions/test_literals.py
+++ b/python/tests/expressions/test_literals.py
@@ -14,6 +14,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
+# pylint:disable=eval-used
+
import datetime
import uuid
from decimal import Decimal
@@ -21,15 +23,17 @@ from decimal import Decimal
import pytest
from pyiceberg.expressions.literals import (
- AboveMax,
- BelowMin,
BinaryLiteral,
BooleanLiteral,
DateLiteral,
DecimalLiteral,
DoubleLiteral,
FixedLiteral,
+ FloatAboveMax,
+ FloatBelowMin,
FloatLiteral,
+ IntAboveMax,
+ IntBelowMin,
LongLiteral,
StringLiteral,
TimeLiteral,
@@ -53,8 +57,6 @@ from pyiceberg.types import (
UUIDType,
)
-# Base
-
def test_literal_from_none_error():
with pytest.raises(TypeError) as e:
@@ -147,11 +149,11 @@ def test_long_to_integer_within_bound():
def test_long_to_integer_outside_bound():
big_lit = literal(IntegerType.max + 1).to(LongType())
above_max_lit = big_lit.to(IntegerType())
- assert above_max_lit == AboveMax()
+ assert above_max_lit == IntAboveMax()
small_lit = literal(IntegerType.min - 1).to(LongType())
below_min_lit = small_lit.to(IntegerType())
- assert below_min_lit == BelowMin()
+ assert below_min_lit == IntBelowMin()
def test_long_to_float_conversion():
@@ -218,11 +220,11 @@ def test_double_to_float_within_bound():
def test_double_to_float_outside_bound():
big_lit = literal(FloatType.max + 1.0e37).to(DoubleType())
above_max_lit = big_lit.to(FloatType())
- assert above_max_lit == AboveMax()
+ assert above_max_lit == FloatAboveMax()
small_lit = literal(FloatType.min - 1.0e37).to(DoubleType())
below_min_lit = small_lit.to(FloatType())
- assert below_min_lit == BelowMin()
+ assert below_min_lit == FloatBelowMin()
@pytest.mark.parametrize(
@@ -239,9 +241,15 @@ def test_decimal_to_decimal_conversion():
assert lit.value.as_tuple() == lit.to(DecimalType(9, 2)).value.as_tuple()
assert lit.value.as_tuple() == lit.to(DecimalType(11, 2)).value.as_tuple()
- assert lit.to(DecimalType(9, 0)) is None
- assert lit.to(DecimalType(9, 1)) is None
- assert lit.to(DecimalType(9, 3)) is None
+ with pytest.raises(ValueError) as e:
+ _ = lit.to(DecimalType(9, 0))
+ assert "Could not convert 34.11 into a decimal(9, 0)" in str(e.value)
+ with pytest.raises(ValueError) as e:
+ _ = lit.to(DecimalType(9, 1))
+ assert "Could not convert 34.11 into a decimal(9, 1)" in str(e.value)
+ with pytest.raises(ValueError) as e:
+ _ = lit.to(DecimalType(9, 3))
+ assert "Could not convert 34.11 into a decimal(9, 3)" in str(e.value)
def test_timestamp_to_date():
@@ -251,15 +259,15 @@ def test_timestamp_to_date():
assert date_lit.value == 0
-# STRING
-
-
def test_string_literal():
sqrt2 = literal("1.414").to(StringType())
pi = literal("3.141").to(StringType())
pi_string_lit = StringLiteral("3.141")
pi_double_lit = literal(3.141).to(DoubleType())
+ assert literal("3.141").to(IntegerType()) == literal(3)
+ assert literal("3.141").to(LongType()) == literal(3)
+
assert sqrt2 != pi
assert pi != pi_double_lit
assert pi == pi_string_lit
@@ -312,12 +320,16 @@ def test_string_to_timestamp_literal():
def test_timestamp_with_zone_without_zone_in_literal():
timestamp_str = literal("2017-08-18T14:21:01.919234")
- assert timestamp_str.to(TimestamptzType()) is None
+ with pytest.raises(ValueError) as e:
+ _ = timestamp_str.to(timestamp_str.to(TimestamptzType()))
+ assert "Invalid timestamp with zone: 2017-08-18T14:21:01.919234 (must be ISO-8601)" in str(e.value)
def test_timestamp_without_zone_with_zone_in_literal():
timestamp_str = literal("2017-08-18T14:21:01.919234+07:00")
- assert timestamp_str.to(TimestampType()) is None
+ with pytest.raises(ValueError) as e:
+ _ = timestamp_str.to(TimestampType())
+ assert "Could not convert 2017-08-18T14:21:01.919234+07:00 into a timestamp" in str(e.value)
def test_string_to_uuid_literal():
@@ -431,12 +443,18 @@ def test_binary_to_fixed():
fixed_lit = lit.to(FixedType(3))
assert fixed_lit is not None
assert lit.value == fixed_lit.value
- assert lit.to(FixedType(4)) is None
+
+ with pytest.raises(TypeError) as e:
+ _ = lit.to(FixedType(4))
+ assert "Cannot convert BinaryLiteral into fixed[4], different length: 4 <> 3" in str(e.value)
def test_binary_to_smaller_fixed_none():
lit = literal(bytearray([0x00, 0x01, 0x02]))
- assert lit.to(FixedType(2)) is None
+
+ with pytest.raises(TypeError) as e:
+ _ = lit.to(FixedType(2))
+ assert "Cannot convert BinaryLiteral into fixed[2], different length: 2 <> 3" in str(e.value)
def test_fixed_to_binary():
@@ -448,35 +466,61 @@ def test_fixed_to_binary():
def test_fixed_to_smaller_fixed_none():
lit = literal(bytearray([0x00, 0x01, 0x02])).to(FixedType(3))
- assert lit.to(FixedType(2)) is None
+ with pytest.raises(ValueError) as e:
+ lit.to(lit.to(FixedType(2)))
+ assert "Could not convert b'\\x00\\x01\\x02' into a fixed[2]" in str(e.value)
-def test_above_max():
- a = AboveMax()
+def test_above_max_float():
+ a = FloatAboveMax()
# singleton
- assert a == AboveMax()
- assert str(a) == "AboveMax"
- assert repr(a) == "AboveMax()"
- with pytest.raises(ValueError) as e:
- a.value()
- assert "AboveMax has no value" in str(e.value)
+ assert a == FloatAboveMax()
+ assert str(a) == "FloatAboveMax"
+ assert repr(a) == "FloatAboveMax()"
+ assert a.value == FloatType.max
+ assert a == eval(repr(a))
with pytest.raises(TypeError) as e:
a.to(IntegerType())
- assert "Cannot change the type of AboveMax" in str(e.value)
+ assert "Cannot change the type of FloatAboveMax" in str(e.value)
-def test_below_min():
- b = BelowMin()
+def test_below_min_float():
+ b = FloatBelowMin()
# singleton
- assert b == BelowMin()
- assert str(b) == "BelowMin"
- assert repr(b) == "BelowMin()"
- with pytest.raises(ValueError) as e:
- b.value()
- assert "BelowMin has no value" in str(e.value)
+ assert b == FloatBelowMin()
+ assert str(b) == "FloatBelowMin"
+ assert repr(b) == "FloatBelowMin()"
+ assert b == eval(repr(b))
+ assert b.value == FloatType.min
with pytest.raises(TypeError) as e:
b.to(IntegerType())
- assert "Cannot change the type of BelowMin" in str(e.value)
+ assert "Cannot change the type of FloatBelowMin" in str(e.value)
+
+
+def test_above_max_int():
+ a = IntAboveMax()
+ # singleton
+ assert a == IntAboveMax()
+ assert str(a) == "IntAboveMax"
+ assert repr(a) == "IntAboveMax()"
+ assert a.value == IntegerType.max
+ assert a == eval(repr(a))
+ with pytest.raises(TypeError) as e:
+ a.to(IntegerType())
+ assert "Cannot change the type of IntAboveMax" in str(e.value)
+
+
+def test_below_min_int():
+ b = IntBelowMin()
+ # singleton
+ assert b == IntBelowMin()
+ assert str(b) == "IntBelowMin"
+ assert repr(b) == "IntBelowMin()"
+ assert b == eval(repr(b))
+ assert b.value == IntegerType.min
+ with pytest.raises(TypeError) as e:
+ b.to(IntegerType())
+ assert "Cannot change the type of IntBelowMin" in str(e.value)
def test_invalid_boolean_conversions():
@@ -531,7 +575,8 @@ def test_invalid_long_conversions():
],
)
def test_invalid_float_conversions(lit, test_type):
- assert lit.to(test_type) is None
+ with pytest.raises(TypeError):
+ _ = lit.to(test_type)
@pytest.mark.parametrize("lit", [literal("2017-08-18").to(DateType())])
@@ -597,6 +642,13 @@ def test_invalid_timestamp_conversions():
)
+def test_invalid_decimal_conversion_scale():
+ lit = literal(Decimal("34.11"))
+ with pytest.raises(ValueError) as e:
+ lit.to(DecimalType(9, 4))
+ assert "Could not convert 34.11 into a decimal(9, 4)" in str(e.value)
+
+
def test_invalid_decimal_conversions():
assert_invalid_conversions(
literal(Decimal("34.11")),
@@ -610,7 +662,6 @@ def test_invalid_decimal_conversions():
TimeType(),
TimestampType(),
TimestamptzType(),
- DecimalType(9, 4),
StringType(),
UUIDType(),
FixedType(1),
@@ -622,7 +673,7 @@ def test_invalid_decimal_conversions():
def test_invalid_string_conversions():
assert_invalid_conversions(
literal("abc"),
- [BooleanType(), IntegerType(), LongType(), FloatType(), DoubleType(), FixedType(1), BinaryType()],
+ [BooleanType(), FloatType(), DoubleType(), FixedType(1), BinaryType()],
)
@@ -689,4 +740,5 @@ def test_invalid_binary_conversions():
def assert_invalid_conversions(lit, types=None):
for type_var in types:
- assert lit.to(type_var) is None
+ with pytest.raises(TypeError):
+ _ = lit.to(type_var)