diff --git a/ibis/common/deferred.py b/ibis/common/deferred.py index f109ee947a39a..bcf5458ec52c0 100644 --- a/ibis/common/deferred.py +++ b/ibis/common/deferred.py @@ -8,67 +8,6 @@ from koerce import Builder, Call, Deferred, Var, _ -def _contains_deferred(obj: Any) -> bool: - if isinstance(obj, (Builder, Deferred)): - return True - elif (typ := type(obj)) in (tuple, list, set): - return any(_contains_deferred(o) for o in obj) - elif typ is dict: - return any(_contains_deferred(o) for o in obj.values()) - return False - - -F = TypeVar("F", bound=Callable) - - -@overload -def deferrable(*, repr: str | None = None) -> Callable[[F], F]: ... - - -@overload -def deferrable(func: F) -> F: ... - - -def deferrable(func=None, *, repr=None): - """Wrap a top-level expr function to support deferred arguments. - - When a deferrable function is called, the args & kwargs are traversed to - look for `Deferred` values (through builtin collections like - `list`/`tuple`/`set`/`dict`). If any `Deferred` arguments are found, then - the result is also `Deferred`. Otherwise the function is called directly. - - Parameters - ---------- - func - A callable to make deferrable - repr - An optional fixed string to use when repr-ing the deferred expression, - instead of the usual. This is useful for complex deferred expressions - where the arguments don't necessarily make sense to be user facing - in the repr. - - """ - - def wrapper(func): - # Parse the signature of func so we can validate deferred calls eagerly, - # erroring for invalid/missing arguments at call time not resolve time. - sig = inspect.signature(func) - - @functools.wraps(func) - def inner(*args, **kwargs): - if _contains_deferred((args, kwargs)): - # Try to bind the arguments now, raising a nice error - # immediately if the function was called incorrectly - sig.bind(*args, **kwargs) - builder = Call(func, *args, **kwargs) - return Deferred(builder) # , repr=repr) - return func(*args, **kwargs) - - return inner # type: ignore - - return wrapper if func is None else wrapper(func) - - class _Variable(Var): def __repr__(self): return self.name diff --git a/ibis/common/dispatch.py b/ibis/common/dispatch.py index b023cab000b4e..fad02381ea4b3 100644 --- a/ibis/common/dispatch.py +++ b/ibis/common/dispatch.py @@ -6,13 +6,9 @@ import re from collections import defaultdict from types import UnionType -from typing import Union +from typing import Union, get_args, get_origin -from ibis.common.typing import ( - evaluate_annotations, - get_args, - get_origin, -) +from ibis.common.typing import evaluate_annotations from ibis.util import import_object, unalias_package diff --git a/ibis/common/tests/test_deferred.py b/ibis/common/tests/test_deferred.py deleted file mode 100644 index 9afce28f8c1fb..0000000000000 --- a/ibis/common/tests/test_deferred.py +++ /dev/null @@ -1,614 +0,0 @@ -# from __future__ import annotations - -# import operator -# import pickle - -# import pytest -# from pytest import param - -# import ibis -# from ibis.common.bases import Slotted -# from ibis.common.collections import FrozenDict -# from ibis.common.deferred import ( -# Attr, -# Call, -# Deferred, -# Factory, -# Item, -# Just, -# JustUnhashable, -# Mapping, -# Sequence, -# Variable, -# _, -# const, -# deferrable, -# deferred, -# resolver, -# var, -# ) -# from ibis.util import Namespace - - -# def test_builder_just(): -# p = Just(1) -# assert p.resolve({}) == 1 -# assert p.resolve({"a": 1}) == 1 - -# # unwrap subsequently nested Just instances -# assert Just(p) == p - -# # disallow creating a Just builder from other builders or deferreds -# with pytest.raises(TypeError, match="cannot be used as a Just value"): -# Just(_) -# with pytest.raises(TypeError, match="cannot be used as a Just value"): -# Just(Factory(lambda _: _)) - - -# @pytest.mark.parametrize( -# "value", -# [ -# [1, 2, 3], -# {"a": 1, "b": 2}, -# {1, 2, 3}, -# ], -# ) -# def test_builder_just_unhashable(value): -# p = Just(value) -# assert isinstance(p, JustUnhashable) -# assert p.resolve({}) == value - - -# def test_builder_variable(): -# p = Variable("other") -# context = {"other": 10} -# assert p.resolve(context) == 10 - - -# def test_builder_factory(): -# f = Factory(lambda _: _ + 1) -# assert f.resolve({"_": 1}) == 2 -# assert f.resolve({"_": 2}) == 3 - -# def fn(**kwargs): -# assert kwargs == {"_": 10, "a": 5} -# return -1 - -# f = Factory(fn) -# assert f.resolve({"_": 10, "a": 5}) == -1 - - -# def test_builder_call(): -# def fn(a, b, c=1): -# return a + b + c - -# c = Call(fn, 1, 2, c=3) -# assert c.resolve({}) == 6 - -# c = Call(fn, Just(-1), Just(-2)) -# assert c.resolve({}) == -2 - -# c = Call(dict, a=1, b=2) -# assert c.resolve({}) == {"a": 1, "b": 2} - -# c = Call(float, "1.1") -# assert c.resolve({}) == 1.1 - - -# def test_builder_attr(): -# class MyType: -# def __init__(self, a, b): -# self.a = a -# self.b = b - -# def __hash__(self): -# return hash((type(self), self.a, self.b)) - -# v = Variable("v") -# b = Attr(v, "b") -# assert b.resolve({"v": MyType(1, 2)}) == 2 - -# b = Attr(MyType(1, 2), "a") -# assert b.resolve({}) == 1 - -# name = Variable("name") -# # test that name can be a deferred as well -# b = Attr(v, name) -# assert b.resolve({"v": MyType(1, 2), "name": "a"}) == 1 - - -# def test_builder_item(): -# v = Variable("v") -# b = Item(v, 1) -# assert b.resolve({"v": [1, 2, 3]}) == 2 - -# b = Item(FrozenDict(a=1, b=2), "a") -# assert b.resolve({}) == 1 - -# name = Variable("name") -# # test that name can be a deferred as well -# b = Item(v, name) -# assert b.resolve({"v": {"a": 1, "b": 2}, "name": "b"}) == 2 - - -# def test_builder_mapping(): -# b = Mapping({"a": 1, "b": 2}) -# assert b.resolve({}) == {"a": 1, "b": 2} - -# b = Mapping({"a": Just(1), "b": Just(2)}) -# assert b.resolve({}) == {"a": 1, "b": 2} - -# b = Mapping({"a": Just(1), "b": Just(2), "c": _}) -# assert b.resolve({"_": 3}) == {"a": 1, "b": 2, "c": 3} - - -# def test_builder(): -# class MyClass: -# pass - -# def fn(x): -# return x + 1 - -# assert resolver(1) == Just(1) -# assert resolver(Just(1)) == Just(1) -# assert resolver(Just(Just(1))) == Just(1) -# assert resolver(MyClass) == Just(MyClass) -# assert resolver(fn) == Just(fn) -# assert resolver(()) == Sequence(()) -# assert resolver((1, 2, _)) == Sequence((Just(1), Just(2), _)) -# assert resolver({}) == Mapping({}) -# assert resolver({"a": 1, "b": _}) == Mapping({"a": Just(1), "b": _}) - -# assert resolver(var("x")) == Variable("x") -# assert resolver(Variable("x")) == Variable("x") - - -# def test_builder_objects_are_hashable(): -# a = Variable("a") -# b = Attr(a, "b") -# c = Item(a, 1) -# d = Call(operator.add, a, 1) - -# set_ = {a, b, c, d} -# assert len(set_) == 4 - -# for obj in [a, b, c, d]: -# assert obj == obj -# assert hash(obj) == hash(obj) -# set_.add(obj) -# assert len(set_) == 4 - - -# @pytest.mark.parametrize( -# ("value", "expected"), -# [ -# ((), ()), -# ([], []), -# ({}, {}), -# ((1, 2, 3), (1, 2, 3)), -# ([1, 2, 3], [1, 2, 3]), -# ({"a": 1, "b": 2}, {"a": 1, "b": 2}), -# (FrozenDict({"a": 1, "b": 2}), FrozenDict({"a": 1, "b": 2})), -# ], -# ) -# def test_deferred_builds(value, expected): -# assert resolver(value).resolve({}) == expected - - -# def test_deferred_supports_string_arguments(): -# # deferred() is applied on all arguments of Call() except the first one and -# # sequences are transparently handled, the check far sequences was incorrect -# # for strings causing infinite recursion -# b = resolver("3.14") -# assert b.resolve({}) == "3.14" - - -# def test_deferred_object_are_not_hashable(): -# # since __eq__ is overloaded, Deferred objects are not hashable -# with pytest.raises(TypeError, match="unhashable type"): -# hash(_.a) - - -# def test_deferred_const(): -# obj = const({"a": 1, "b": 2, "c": "gamma"}) - -# deferred = obj["c"].upper() -# assert deferred._resolver == Call(Attr(Item(obj, "c"), "upper")) -# assert deferred.resolve() == "GAMMA" - - -# def test_deferred_variable_getattr(): -# v = var("v") -# p = v.copy -# assert resolver(p) == Attr(v, "copy") -# assert resolver(p).resolve({"v": [1, 2, 3]})() == [1, 2, 3] - -# p = v.copy() -# assert resolver(p) == Call(Attr(v, "copy")) -# assert resolver(p).resolve({"v": [1, 2, 3]}) == [1, 2, 3] - - -# class TableMock(dict): -# def __getattr__(self, attr): -# return self[attr] - -# def __eq__(self, other): -# return isinstance(other, TableMock) and super().__eq__(other) - - -# def _binop(name, switch=False): -# def method(self, other): -# if switch: -# return BinaryMock(name=name, left=other, right=self) -# else: -# return BinaryMock(name=name, left=self, right=other) - -# return method - - -# class ValueMock(Slotted): -# def log(self, base=None): -# return UnaryMock(name="log", arg=base) - -# def sum(self): -# return UnaryMock(name="sum", arg=self) - -# def __neg__(self): -# return UnaryMock(name="neg", arg=self) - -# def __invert__(self): -# return UnaryMock(name="invert", arg=self) - -# __lt__ = _binop("lt") -# __gt__ = _binop("gt") -# __le__ = _binop("le") -# __ge__ = _binop("ge") -# __add__ = _binop("add") -# __radd__ = _binop("add", switch=True) -# __sub__ = _binop("sub") -# __rsub__ = _binop("sub", switch=True) -# __mul__ = _binop("mul") -# __rmul__ = _binop("mul", switch=True) -# __mod__ = _binop("mod") -# __rmod__ = _binop("mod", switch=True) -# __truediv__ = _binop("div") -# __rtruediv__ = _binop("div", switch=True) -# __floordiv__ = _binop("floordiv") -# __rfloordiv__ = _binop("floordiv", switch=True) -# __rshift__ = _binop("shift") -# __rrshift__ = _binop("shift", switch=True) -# __lshift__ = _binop("shift") -# __rlshift__ = _binop("shift", switch=True) -# __pow__ = _binop("pow") -# __rpow__ = _binop("pow", switch=True) -# __xor__ = _binop("xor") -# __rxor__ = _binop("xor", switch=True) -# __and__ = _binop("and") -# __rand__ = _binop("and", switch=True) -# __or__ = _binop("or") -# __ror__ = _binop("or", switch=True) - - -# class ColumnMock(ValueMock): -# __slots__ = ("name", "dtype") - -# def __init__(self, name, dtype): -# super().__init__(name=name, dtype=dtype) - -# def __deferred_repr__(self): -# return f"" - -# def type(self): -# return self.dtype - - -# class UnaryMock(ValueMock): -# __slots__ = ("name", "arg") - -# def __init__(self, name, arg): -# super().__init__(name=name, arg=arg) - - -# class BinaryMock(ValueMock): -# __slots__ = ("name", "left", "right") - -# def __init__(self, name, left, right): -# super().__init__(name=name, left=left, right=right) - - -# @pytest.fixture -# def table(): -# return TableMock( -# a=ColumnMock(name="a", dtype="int"), -# b=ColumnMock(name="b", dtype="int"), -# c=ColumnMock(name="c", dtype="string"), -# ) - - -# @pytest.mark.parametrize( -# "func", -# [ -# param(lambda _: _, id="root"), -# param(lambda _: _.a, id="getattr"), -# param(lambda _: _["a"], id="getitem"), -# param(lambda _: _.a.log(), id="method"), -# param(lambda _: _.a.log(_.b), id="method-with-args"), -# param(lambda _: _.a.log(base=_.b), id="method-with-kwargs"), -# param(lambda _: _.a + _.b, id="binary-op"), -# param(lambda _: ~_.a, id="unary-op"), -# ], -# ) -# def test_deferred_is_pickleable(func, table): -# expr1 = func(_) -# builder1 = resolver(expr1) -# builder2 = pickle.loads(pickle.dumps(builder1)) - -# r1 = builder1.resolve({"_": table}) -# r2 = builder2.resolve({"_": table}) - -# assert r1 == r2 - - -# def test_deferred_getitem(table): -# expr = _["a"] -# assert expr.resolve(table) == table["a"] -# assert repr(expr) == "_['a']" - - -# def test_deferred_getattr(table): -# expr = _.a -# assert expr.resolve(table) == table.a -# assert repr(expr) == "_.a" - - -# def test_deferred_call(table): -# expr = Deferred(Call(operator.add, _.a, 2)) -# res = expr.resolve(table) -# assert res == table.a + 2 -# assert repr(expr) == "add(_.a, 2)" - -# func = lambda a, b: a + b -# expr = Deferred(Call(func, a=_.a, b=2)) -# res = expr.resolve(table) -# assert res == table.a + 2 -# assert func.__name__ in repr(expr) -# assert "a=_.a, b=2" in repr(expr) - -# expr = Deferred(Call(operator.add, (_.a, 2)), repr="") -# assert repr(expr) == "" - - -# def test_deferred_method(table): -# expr = _.a.log() -# res = expr.resolve(table) -# assert res == table.a.log() -# assert repr(expr) == "_.a.log()" - - -# def test_deferred_method_with_args(table): -# expr = _.a.log(1) -# res = expr.resolve(table) -# assert res == table.a.log(1) -# assert repr(expr) == "_.a.log(1)" - -# expr = _.a.log(_.b) -# res = expr.resolve(table) -# assert res == table.a.log(table.b) -# assert repr(expr) == "_.a.log(_.b)" - - -# def test_deferred_method_with_kwargs(table): -# expr = _.a.log(base=1) -# res = expr.resolve(table) -# assert res == table.a.log(base=1) -# assert repr(expr) == "_.a.log(base=1)" - -# expr = _.a.log(base=_.b) -# res = expr.resolve(table) -# assert res == table.a.log(base=table.b) -# assert repr(expr) == "_.a.log(base=_.b)" - - -# def test_deferred_apply(table): -# expr = Deferred(Call(operator.add, _.a, 2)) -# res = expr.resolve(table) -# assert res == table.a + 2 -# assert repr(expr) == "add(_.a, 2)" - -# func = lambda a, b: a + b -# expr = Deferred(Call(func, _.a, 2)) -# res = expr.resolve(table) -# assert res == table.a + 2 -# assert func.__name__ in repr(expr) - - -# @pytest.mark.parametrize( -# "symbol, op", -# [ -# ("+", operator.add), -# ("-", operator.sub), -# ("*", operator.mul), -# ("/", operator.truediv), -# ("//", operator.floordiv), -# ("**", operator.pow), -# ("%", operator.mod), -# ("&", operator.and_), -# ("|", operator.or_), -# ("^", operator.xor), -# (">>", operator.rshift), -# ("<<", operator.lshift), -# ], -# ) -# def test_deferred_binary_operations(symbol, op, table): -# expr = op(_.a, _.b) -# sol = op(table.a, table.b) -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == f"(_.a {symbol} _.b)" - -# expr = op(1, _.a) -# sol = op(1, table.a) -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == f"(1 {symbol} _.a)" - - -# @pytest.mark.parametrize( -# "sym, rsym, op", -# [ -# ("==", "==", operator.eq), -# ("!=", "!=", operator.ne), -# ("<", ">", operator.lt), -# ("<=", ">=", operator.le), -# (">", "<", operator.gt), -# (">=", "<=", operator.ge), -# ], -# ) -# def test_deferred_compare_operations(sym, rsym, op, table): -# expr = op(_.a, _.b) -# sol = op(table.a, table.b) -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == f"(_.a {sym} _.b)" - -# expr = op(1, _.a) -# sol = op(1, table.a) -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == f"(_.a {rsym} 1)" - - -# @pytest.mark.parametrize( -# "symbol, op", -# [ -# ("-", operator.neg), -# ("~", operator.invert), -# ], -# ) -# def test_deferred_unary_operations(symbol, op, table): -# expr = op(_.a) -# sol = op(table.a) -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == f"{symbol}_.a" - - -# @pytest.mark.parametrize("obj", [_, _.a, _.a.b[0]]) -# def test_deferred_is_not_iterable(obj): -# with pytest.raises(TypeError, match="object is not iterable"): -# sorted(obj) - -# with pytest.raises(TypeError, match="object is not iterable"): -# iter(obj) - -# with pytest.raises(TypeError, match="is not an iterator"): -# next(obj) - - -# @pytest.mark.parametrize("obj", [_, _.a, _.a.b[0]]) -# def test_deferred_is_not_truthy(obj): -# with pytest.raises( -# TypeError, match="The truth value of Deferred objects is not defined" -# ): -# bool(obj) - - -# def test_deferrable(table): -# @deferrable -# def f(a, b, c=3): -# return a + b + c - -# assert f(table.a, table.b) == table.a + table.b + 3 -# assert f(table.a, table.b, c=4) == table.a + table.b + 4 - -# expr = f(_.a, _.b) -# sol = table.a + table.b + 3 -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == "f(_.a, _.b)" - -# expr = f(1, 2, c=_.a) -# sol = 3 + table.a -# res = expr.resolve(table) -# assert res == sol -# assert repr(expr) == "f(1, 2, c=_.a)" - -# with pytest.raises(TypeError, match="unknown"): -# f(_.a, _.b, unknown=3) # invalid calls caught at call time - - -# def test_deferrable_repr(): -# @deferrable(repr="") -# def myfunc(x): -# return x + 1 - -# assert repr(myfunc(_.a)) == "" - - -# def test_deferred_set_raises(): -# with pytest.raises(TypeError, match="unhashable type"): -# {_.a, _.b} # noqa: B018 - - -# @pytest.mark.parametrize( -# "case", -# [ -# param(lambda: ([1, _], [1, 2]), id="list"), -# param(lambda: ((1, _), (1, 2)), id="tuple"), -# param(lambda: ({"x": 1, "y": _}, {"x": 1, "y": 2}), id="dict"), -# param(lambda: ({"x": 1, "y": [_, 3]}, {"x": 1, "y": [2, 3]}), id="nested"), -# ], -# ) -# def test_deferrable_nested_args(case): -# arg, sol = case() - -# @deferrable -# def identity(x): -# return x - -# expr = identity(arg) -# assert expr.resolve(2) == sol -# assert identity(sol) is sol -# assert repr(expr) == f"identity({arg!r})" - - -# def test_deferred_is_final(): -# with pytest.raises(TypeError, match="Cannot inherit from final class"): - -# class MyDeferred(Deferred): -# pass - - -# def test_deferred_is_immutable(): -# with pytest.raises(AttributeError, match="cannot be assigned to immutable"): -# _.a = 1 - - -# def test_deferred_namespace(table): -# ns = Namespace(deferred, module=__name__) - -# assert isinstance(ns.ColumnMock, Deferred) -# assert resolver(ns.ColumnMock) == Just(ColumnMock) - -# d = ns.ColumnMock("a", "int") -# assert resolver(d) == Call(Just(ColumnMock), Just("a"), Just("int")) -# assert d.resolve() == ColumnMock("a", "int") - -# d = ns.ColumnMock("a", _) -# assert resolver(d) == Call(Just(ColumnMock), Just("a"), _) -# assert d.resolve("int") == ColumnMock("a", "int") - -# a, b = var("a"), var("b") -# d = ns.ColumnMock(a, b).name -# assert d.resolve(a="colname", b="float") == "colname" - - -# def test_custom_deferred_repr(table): -# expr = _.x + table.a -# assert repr(expr) == "(_.x + )" - - -# def test_null_deferrable(table): -# result = ibis.null(_.a.type()).resolve(table).op() -# expected = ibis.null(table.a.type()).op() -# assert result == expected diff --git a/ibis/common/tests/test_typing.py b/ibis/common/tests/test_typing.py index 9c4c1c5d21250..8f75dacce1751 100644 --- a/ibis/common/tests/test_typing.py +++ b/ibis/common/tests/test_typing.py @@ -8,7 +8,6 @@ DefaultTypeVars, Sentinel, evaluate_annotations, - get_type_hints, ) T = TypeVar("T") @@ -49,27 +48,6 @@ def test_evaluate_annotations_with_self() -> None: assert hints == {"a": Union[int, myhint], "b": Optional[myhint]} -def test_get_type_hints() -> None: - hints = get_type_hints(My) - assert hints == {"a": T, "b": S, "c": str} - - hints = get_type_hints(My, include_properties=True) - assert hints == {"a": T, "b": S, "c": str, "d": Optional[str], "e": U} - - hints = get_type_hints(MyChild, include_properties=True) - assert hints == {"a": T, "b": S, "c": str, "d": Optional[str], "e": U} - - # test that we don't actually mutate the My.__annotations__ - hints = get_type_hints(My) - assert hints == {"a": T, "b": S, "c": str} - - hints = get_type_hints(example) - assert hints == {"a": int, "b": str, "return": str} - - hints = get_type_hints(example, include_properties=True) - assert hints == {"a": int, "b": str, "return": str} - - class A(Generic[T, S, U]): a: int b: str diff --git a/ibis/common/typing.py b/ibis/common/typing.py index d482a62bfbc86..0d6ba3d39301f 100644 --- a/ibis/common/typing.py +++ b/ibis/common/typing.py @@ -1,20 +1,10 @@ from __future__ import annotations import inspect -import re import sys -from abc import abstractmethod from itertools import zip_longest -from typing import TYPE_CHECKING, Any, Optional, TypeVar, get_args, get_origin -from typing import get_type_hints as _get_type_hints - -from koerce.utils import get_type_hints - -if TYPE_CHECKING: - from typing_extensions import Self - from types import UnionType -from typing import TypeAlias +from typing import Any, Optional, TypeAlias, TypeVar # Keep this alias in sync with unittest.case._ClassInfo _ClassInfo: TypeAlias = type | UnionType | tuple["_ClassInfo", ...] @@ -79,19 +69,6 @@ def evaluate_annotations( return result -def format_typehint(typ: Any) -> str: - if isinstance(typ, type): - return typ.__name__ - elif isinstance(typ, TypeVar): - if typ.__bound__ is None: - return str(typ) - else: - return format_typehint(typ.__bound__) - else: - # remove the module name from the typehint, including generics - return re.sub(r"(\w+\.)+", "", str(typ)) - - class DefaultTypeVars: """Enable using default type variables in generic classes (PEP-0696).""" diff --git a/ibis/expr/api.py b/ibis/expr/api.py index aebf960bfc915..6baa2980bdf1e 100644 --- a/ibis/expr/api.py +++ b/ibis/expr/api.py @@ -11,7 +11,7 @@ from collections import Counter from typing import TYPE_CHECKING, Any, overload -from koerce import Annotable, Deferred +from koerce import Annotable, Deferred, Var, deferrable import ibis.expr.builders as bl import ibis.expr.datatypes as dt @@ -20,7 +20,6 @@ import ibis.expr.types as ir from ibis import selectors, util from ibis.backends import BaseBackend, connect -from ibis.common.deferred import _, deferrable from ibis.common.dispatch import lazy_singledispatch from ibis.common.exceptions import IbisInputError from ibis.common.temporal import normalize_datetime, normalize_timezone @@ -197,7 +196,13 @@ pi = ops.Pi().to_expr() -deferred = _ +class _Variable(Var): + def __repr__(self): + return self.name + + +# reserved variable name for the value being matched +deferred = _ = Deferred(_Variable("_")) """Deferred expression object. Use this object to refer to a previous table expression in a chain of diff --git a/ibis/expr/builders.py b/ibis/expr/builders.py index 83bd7d142d2ad..ad4095ccb1e69 100644 --- a/ibis/expr/builders.py +++ b/ibis/expr/builders.py @@ -3,7 +3,7 @@ import math from typing import TYPE_CHECKING, Any, Literal, Optional, Union -from koerce import Annotable, Deferred, annotated, attribute +from koerce import Annotable, Deferred, annotated, attribute, deferrable from koerce import Builder as Resolver import ibis @@ -12,7 +12,6 @@ import ibis.expr.rules as rlz import ibis.expr.types as ir from ibis import util -from ibis.common.deferred import deferrable from ibis.common.exceptions import IbisInputError from ibis.common.selectors import Selector # noqa: TCH001 from ibis.common.typing import VarTuple # noqa: TCH001 diff --git a/ibis/expr/datatypes/core.py b/ibis/expr/datatypes/core.py index 94c433d2f5a30..3efde71630cdc 100644 --- a/ibis/expr/datatypes/core.py +++ b/ibis/expr/datatypes/core.py @@ -135,7 +135,7 @@ def __coerce__(cls, value, **kwargs): try: return dtype(value) except (TypeError, RuntimeError) as e: - raise CoercionError("Unable to coerce to a DataType") from e + raise ValueError("Unable to coerce to a DataType") from e def __call__(self, **kwargs): return self.copy(**kwargs) diff --git a/ibis/expr/datatypes/tests/test_core.py b/ibis/expr/datatypes/tests/test_core.py index 67d286b141eca..cc48dcbca40f5 100644 --- a/ibis/expr/datatypes/tests/test_core.py +++ b/ibis/expr/datatypes/tests/test_core.py @@ -7,7 +7,7 @@ from typing import Annotated, NamedTuple import pytest -from koerce import As, Pattern +from koerce import As, MatchError, Object, Pattern from pytest import param import ibis.expr.datatypes as dt @@ -432,25 +432,20 @@ def test_struct_equality(): assert st3 != st2 -def test_singleton_null(): - assert dt.null is dt.Null() - - -def test_singleton_boolean(): +def test_booleqn_equality(): assert dt.Boolean() == dt.boolean - assert dt.Boolean() is dt.boolean - assert dt.Boolean() is dt.Boolean() - assert dt.Boolean(nullable=True) is dt.boolean - assert dt.Boolean(nullable=False) is not dt.boolean - assert dt.Boolean(nullable=False) is dt.Boolean(nullable=False) - assert dt.Boolean(nullable=True) is dt.Boolean(nullable=True) - assert dt.Boolean(nullable=True) is not dt.Boolean(nullable=False) + assert dt.Boolean() == dt.Boolean() + assert dt.Boolean(nullable=True) == dt.boolean + assert dt.Boolean(nullable=False) != dt.boolean + assert dt.Boolean(nullable=False) == dt.Boolean(nullable=False) + assert dt.Boolean(nullable=True) == dt.Boolean(nullable=True) + assert dt.Boolean(nullable=True) != dt.Boolean(nullable=False) -def test_singleton_primitive(): - assert dt.Int64() is dt.int64 - assert dt.Int64(nullable=False) is not dt.int64 - assert dt.Int64(nullable=False) is dt.Int64(nullable=False) +def test_primite_equality(): + assert dt.Int64() == dt.int64 + assert dt.Int64(nullable=False) != dt.int64 + assert dt.Int64(nullable=False) == dt.Int64(nullable=False) def test_array_type_not_equals(): @@ -636,46 +631,57 @@ def test_set_is_an_alias_of_array(): def test_type_coercion(): - p = Pattern.from_typehint(dt.DataType) + p = Pattern.from_typehint(As[dt.DataType]) assert p.apply(dt.int8, {}) == dt.int8 assert p.apply("int8", {}) == dt.int8 assert p.apply(dt.string, {}) == dt.string assert p.apply("string", {}) == dt.string - assert p.apply(3, {}) is NoMatch + with pytest.raises(MatchError): + p.apply(3) - p = Pattern.from_typehint(dt.Primitive) + p = Pattern.from_typehint(As[dt.Primitive]) assert p.apply(dt.int8, {}) == dt.int8 assert p.apply("int8", {}) == dt.int8 assert p.apply(dt.boolean, {}) == dt.boolean assert p.apply("boolean", {}) == dt.boolean - assert p.apply(dt.Array(dt.int8), {}) is NoMatch - assert p.apply("array", {}) is NoMatch + with pytest.raises(MatchError): + p.apply(dt.Array(dt.int8)) + with pytest.raises(MatchError): + p.apply("array") - p = Pattern.from_typehint(dt.Integer) + p = Pattern.from_typehint(As[dt.Integer]) assert p.apply(dt.int8, {}) == dt.int8 assert p.apply("int8", {}) == dt.int8 assert p.apply(dt.uint8, {}) == dt.uint8 assert p.apply("uint8", {}) == dt.uint8 - assert p.apply(dt.boolean, {}) is NoMatch - assert p.apply("boolean", {}) is NoMatch + with pytest.raises(MatchError): + p.apply(dt.boolean) + with pytest.raises(MatchError): + p.apply("boolean") - p = Pattern.from_typehint(dt.Array[dt.Integer]) + p = Pattern.from_typehint(As[dt.Array[dt.Integer]]) assert p.apply(dt.Array(dt.int8), {}) == dt.Array(dt.int8) assert p.apply("array", {}) == dt.Array(dt.int8) assert p.apply(dt.Array(dt.uint8), {}) == dt.Array(dt.uint8) assert p.apply("array", {}) == dt.Array(dt.uint8) - assert p.apply(dt.Array(dt.boolean), {}) is NoMatch - assert p.apply("array", {}) is NoMatch + with pytest.raises(MatchError): + p.apply(dt.Array(dt.boolean)) + with pytest.raises(MatchError): + p.apply("array") - p = Pattern.from_typehint(dt.Map[dt.String, dt.Integer]) + p = Pattern.from_typehint(As[dt.Map[dt.String, dt.Integer]]) assert p.apply(dt.Map(dt.string, dt.int8), {}) == dt.Map(dt.string, dt.int8) assert p.apply("map", {}) == dt.Map(dt.string, dt.int8) assert p.apply(dt.Map(dt.string, dt.uint8), {}) == dt.Map(dt.string, dt.uint8) assert p.apply("map", {}) == dt.Map(dt.string, dt.uint8) - assert p.apply(dt.Map(dt.string, dt.boolean), {}) is NoMatch - assert p.apply("map", {}) is NoMatch + with pytest.raises(MatchError): + p.apply(dt.Map(dt.string, dt.boolean)) + with pytest.raises(MatchError): + p.apply("map") - p = Pattern.from_typehint(Annotated[dt.Interval, Attrs(unit=As(TimeUnit))]) + p = Pattern.from_typehint( + As[Annotated[dt.Interval, Object(dt.Interval, unit=As(TimeUnit))]] + ) assert p.apply(dt.Interval("s"), {}) == dt.Interval("s") assert p.apply(dt.Interval("ns"), {}) == dt.Interval("ns") diff --git a/ibis/expr/operations/__init__.py b/ibis/expr/operations/__init__.py index ba89a1d4d2d48..77765ebb9e049 100644 --- a/ibis/expr/operations/__init__.py +++ b/ibis/expr/operations/__init__.py @@ -1,23 +1,24 @@ from __future__ import annotations -from ibis.expr.operations.analytic import * # noqa: F403 -from ibis.expr.operations.arrays import * # noqa: F403 -from ibis.expr.operations.core import * # noqa: F403 -from ibis.expr.operations.generic import * # noqa: F403 -from ibis.expr.operations.geospatial import * # noqa: F403 -from ibis.expr.operations.histograms import * # noqa: F403 -from ibis.expr.operations.json import * # noqa: F403 -from ibis.expr.operations.logical import * # noqa: F403 -from ibis.expr.operations.maps import * # noqa: F403 -from ibis.expr.operations.numeric import * # noqa: F403 -from ibis.expr.operations.reductions import * # noqa: F403 -from ibis.expr.operations.relations import * # noqa: F403 -from ibis.expr.operations.sortkeys import * # noqa: F403 -from ibis.expr.operations.strings import * # noqa: F403 -from ibis.expr.operations.structs import * # noqa: F403 -from ibis.expr.operations.subqueries import * # noqa: F403 -from ibis.expr.operations.temporal import * # noqa: F403 -from ibis.expr.operations.temporal_windows import * # noqa: F403 -from ibis.expr.operations.udf import * # noqa: F403 -from ibis.expr.operations.vectorized import * # noqa: F403 -from ibis.expr.operations.window import * # noqa: F403 +# ruff: noqa: I001, F403 +from ibis.expr.operations.analytic import * +from ibis.expr.operations.arrays import * +from ibis.expr.operations.core import * +from ibis.expr.operations.generic import * +from ibis.expr.operations.geospatial import * +from ibis.expr.operations.histograms import * +from ibis.expr.operations.json import * +from ibis.expr.operations.logical import * +from ibis.expr.operations.numeric import * +from ibis.expr.operations.reductions import * +from ibis.expr.operations.relations import * +from ibis.expr.operations.sortkeys import * +from ibis.expr.operations.strings import * +from ibis.expr.operations.structs import * +from ibis.expr.operations.subqueries import * +from ibis.expr.operations.temporal import * +from ibis.expr.operations.temporal_windows import * +from ibis.expr.operations.udf import * +from ibis.expr.operations.vectorized import * +from ibis.expr.operations.window import * +from ibis.expr.operations.maps import * diff --git a/ibis/expr/operations/maps.py b/ibis/expr/operations/maps.py index 41c899d82d6fd..ffc503512c4ab 100644 --- a/ibis/expr/operations/maps.py +++ b/ibis/expr/operations/maps.py @@ -43,16 +43,13 @@ class MapGet(Value): arg: Value[dt.Map] key: Value - default: Optional[Value] = None + default: Value = None shape = rlz.shape_like("args") @attribute def dtype(self): - if self.default is None: - return self.arg.dtype.value_type - else: - return dt.higher_precedence(self.default.dtype, self.arg.dtype.value_type) + return dt.higher_precedence(self.default.dtype, self.arg.dtype.value_type) @public diff --git a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call0-missing_a_required_argument/missing_a_required_argument.txt b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call0-missing_a_required_argument/missing_a_required_argument.txt index 82b70db10e116..038dca5f70e74 100644 --- a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call0-missing_a_required_argument/missing_a_required_argument.txt +++ b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call0-missing_a_required_argument/missing_a_required_argument.txt @@ -1,3 +1 @@ -Literal(1) missing a required argument: 'dtype' - -Expected signature: Literal(value: Annotated[Any, Not(pattern=InstanceOf(type=))], dtype: DataType) \ No newline at end of file +missing a required argument: 'dtype' \ No newline at end of file diff --git a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call1-too_many_positional_arguments/too_many_positional_arguments.txt b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call1-too_many_positional_arguments/too_many_positional_arguments.txt index 5336bc197fbf2..8bca81ebfa831 100644 --- a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call1-too_many_positional_arguments/too_many_positional_arguments.txt +++ b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call1-too_many_positional_arguments/too_many_positional_arguments.txt @@ -1,3 +1 @@ -Literal(1, Int8(nullable=True), 'foo') too many positional arguments - -Expected signature: Literal(value: Annotated[Any, Not(pattern=InstanceOf(type=))], dtype: DataType) \ No newline at end of file +too many positional arguments \ No newline at end of file diff --git a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call2-got_an_unexpected_keyword/got_an_unexpected_keyword.txt b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call2-got_an_unexpected_keyword/got_an_unexpected_keyword.txt index 20cfdafa8a44c..0c3899fa4bd65 100644 --- a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call2-got_an_unexpected_keyword/got_an_unexpected_keyword.txt +++ b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call2-got_an_unexpected_keyword/got_an_unexpected_keyword.txt @@ -1,3 +1 @@ -Literal(1, Int8(nullable=True), name='foo') got an unexpected keyword argument 'name' - -Expected signature: Literal(value: Annotated[Any, Not(pattern=InstanceOf(type=))], dtype: DataType) \ No newline at end of file +got an unexpected keyword argument 'name' \ No newline at end of file diff --git a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call3-multiple_values_for_argument/multiple_values_for_argument.txt b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call3-multiple_values_for_argument/multiple_values_for_argument.txt index c2f857abfdc1e..82797c4f328da 100644 --- a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call3-multiple_values_for_argument/multiple_values_for_argument.txt +++ b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call3-multiple_values_for_argument/multiple_values_for_argument.txt @@ -1,3 +1 @@ -Literal(1, Int8(nullable=True), dtype=Int16(nullable=True)) multiple values for argument 'dtype' - -Expected signature: Literal(value: Annotated[Any, Not(pattern=InstanceOf(type=))], dtype: DataType) \ No newline at end of file +multiple values for argument 'dtype' \ No newline at end of file diff --git a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call4-invalid_dtype/invalid_dtype.txt b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call4-invalid_dtype/invalid_dtype.txt index 68755d5f9c556..57f9b10ae0a44 100644 --- a/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call4-invalid_dtype/invalid_dtype.txt +++ b/ibis/expr/operations/tests/snapshots/test_generic/test_error_message_when_constructing_literal/call4-invalid_dtype/invalid_dtype.txt @@ -1,4 +1 @@ -Literal(1, 4) has failed due to the following errors: - `dtype`: 4 is not coercible to a DataType - -Expected signature: Literal(value: Annotated[Any, Not(pattern=InstanceOf(type=))], dtype: DataType) \ No newline at end of file +`4` cannot be coerced to \ No newline at end of file diff --git a/ibis/expr/operations/udf.py b/ibis/expr/operations/udf.py index 01240d014070c..042e325dff1e9 100644 --- a/ibis/expr/operations/udf.py +++ b/ibis/expr/operations/udf.py @@ -11,7 +11,7 @@ import typing from typing import TYPE_CHECKING, Any, Optional, TypeVar, overload -from koerce import argument, attribute +from koerce import argument, attribute, deferrable from public import public import ibis.common.exceptions as exc @@ -21,7 +21,6 @@ import ibis.expr.rules as rlz from ibis import util from ibis.common.collections import FrozenDict -from ibis.common.deferred import deferrable if TYPE_CHECKING: from collections.abc import Callable, Iterable, MutableMapping diff --git a/ibis/expr/types/arrays.py b/ibis/expr/types/arrays.py index 2af1d0340c4f9..dbdf04192f4b0 100644 --- a/ibis/expr/types/arrays.py +++ b/ibis/expr/types/arrays.py @@ -3,11 +3,10 @@ import inspect from typing import TYPE_CHECKING -from koerce import resolve +from koerce import Deferred, deferrable, resolve from public import public import ibis.expr.operations as ops -from ibis.common.deferred import Deferred, deferrable from ibis.expr.types.generic import Column, Scalar, Value if TYPE_CHECKING: diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 8d3ab6fb3a6ec..a31f2195bfe77 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -3,7 +3,7 @@ from collections.abc import Iterable, Sequence from typing import TYPE_CHECKING, Any -from koerce import Deferred, _, resolve +from koerce import Deferred, _, deferrable, resolve from public import public import ibis @@ -11,7 +11,6 @@ import ibis.expr.builders as bl import ibis.expr.datatypes as dt import ibis.expr.operations as ops -from ibis.common.deferred import deferrable from ibis.expr.rewrites import rewrite_window_input from ibis.expr.types.core import Expr, _binop, _FixedTextJupyterMixin, _is_null_literal from ibis.util import deprecated, promote_list, warn_deprecated diff --git a/ibis/expr/types/maps.py b/ibis/expr/types/maps.py index 8672206a6873e..eb1194b903a1c 100644 --- a/ibis/expr/types/maps.py +++ b/ibis/expr/types/maps.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING, Any +from koerce import deferrable from public import public import ibis.expr.operations as ops -from ibis.common.deferred import deferrable from ibis.expr.types.generic import Column, Scalar, Value if TYPE_CHECKING: diff --git a/ibis/expr/types/relations.py b/ibis/expr/types/relations.py index 50562d6f5a710..308472bcc1d65 100644 --- a/ibis/expr/types/relations.py +++ b/ibis/expr/types/relations.py @@ -3838,7 +3838,7 @@ def pivot_longer( if values_transform is None: values_transform = toolz.identity elif isinstance(values_transform, Deferred): - values_transform = lambda t: resolve(values_transform, _=t) + values_transform = lambda t, what=values_transform: resolve(what, _=t) pieces = [] diff --git a/ibis/expr/types/structs.py b/ibis/expr/types/structs.py index 4c7cdc197094d..163147f9dd943 100644 --- a/ibis/expr/types/structs.py +++ b/ibis/expr/types/structs.py @@ -4,11 +4,11 @@ from keyword import iskeyword from typing import TYPE_CHECKING +from koerce import deferrable from public import public import ibis.expr.operations as ops from ibis import util -from ibis.common.deferred import deferrable from ibis.common.exceptions import IbisError from ibis.expr.types.generic import Column, Scalar, Value, literal diff --git a/ibis/expr/types/temporal_windows.py b/ibis/expr/types/temporal_windows.py index 94dad95a1587b..7d296a6c93dce 100644 --- a/ibis/expr/types/temporal_windows.py +++ b/ibis/expr/types/temporal_windows.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING, Literal -from koerce import Annotable from public import public import ibis.common.exceptions as com