From a7c76d5f25d7e91d42060c3b9a19afa69eaf61db Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:41:28 -0400 Subject: [PATCH] feat(api): support deferred objects in `literal` --- ibis/expr/types/generic.py | 18 ++++++++++++++++++ ibis/tests/expr/test_literal.py | 9 +++++++++ ibis/tests/test_strategies.py | 8 -------- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/ibis/expr/types/generic.py b/ibis/expr/types/generic.py index 662f748d9fc5..979ea4876361 100644 --- a/ibis/expr/types/generic.py +++ b/ibis/expr/types/generic.py @@ -2427,6 +2427,7 @@ def null(type: dt.DataType | str | None = None) -> Value: @public +@deferrable def literal(value: Any, type: dt.DataType | str | None = None) -> Scalar: """Create a scalar expression from a Python value. @@ -2480,6 +2481,23 @@ def literal(value: Any, type: dt.DataType | str | None = None) -> Scalar: Traceback (most recent call last): ... TypeError: Value 'foobar' cannot be safely coerced to int64 + + Literals can also be used in a deferred context. + + Here's an example of constructing a table of a column's type repeated for + every row: + + >>> from ibis import _, selectors as s + >>> ibis.options.interactive = True + >>> t = ibis.examples.penguins.fetch() + >>> t.select(s.across(s.all(), ibis.literal(_.type(), type=str).name(_.get_name()))).head(1) + ┏━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━┓ + ┃ species ┃ island ┃ bill_length_mm ┃ bill_depth_mm ┃ flipper_length_mm ┃ … ┃ + ┡━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━┩ + │ string │ string │ string │ string │ string │ … │ + ├─────────┼────────┼────────────────┼───────────────┼───────────────────┼───┤ + │ string │ string │ float64 │ float64 │ int64 │ … │ + └─────────┴────────┴────────────────┴───────────────┴───────────────────┴───┘ """ if isinstance(value, Expr): node = value.op() diff --git a/ibis/tests/expr/test_literal.py b/ibis/tests/expr/test_literal.py index 0893a7eec4bc..4da79536aaf4 100644 --- a/ibis/tests/expr/test_literal.py +++ b/ibis/tests/expr/test_literal.py @@ -8,6 +8,7 @@ import ibis import ibis.expr.datatypes as dt +from ibis import _ from ibis.common.collections import frozendict from ibis.expr.operations import Literal from ibis.tests.util import assert_pickle_roundtrip @@ -166,3 +167,11 @@ def test_timestamp_literal_without_tz(): def test_integer_as_decimal(): lit = ibis.literal(12, type="decimal") assert lit.op().value == decimal.Decimal(12) + + +def test_deferred(table): + expr = _.g.get_name() + dtype = _.g.type() + deferred = ibis.literal(expr, type=dtype) + result = deferred.resolve(table) + assert result.op().value == "g" diff --git a/ibis/tests/test_strategies.py b/ibis/tests/test_strategies.py index fc0da95c8643..0a6a51f6976a 100644 --- a/ibis/tests/test_strategies.py +++ b/ibis/tests/test_strategies.py @@ -4,12 +4,10 @@ import hypothesis.strategies as st import pytest -import ibis import ibis.expr.datatypes as dt import ibis.expr.schema as sch import ibis.expr.types as ir import ibis.tests.strategies as its -from ibis.common.annotations import ValidationError @h.given(its.null_dtype) @@ -173,10 +171,4 @@ def test_memtable(memtable): assert isinstance(memtable.schema(), sch.Schema) -@h.given(its.all_dtypes()) -def test_deferred_literal(dtype): - with pytest.raises(ValidationError): - ibis.literal(ibis._.a, type=dtype) - - # TODO(kszucs): we enforce field name uniqueness in the schema, but we don't for Struct datatype