From e1374a4fc98ea8a4b7e0df6e6599bcde94c197b1 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sun, 15 May 2022 07:33:08 -0400 Subject: [PATCH] feat(backends): allow column expressions from non-foreign tables on the right side of `isin`/`notin` --- ibis/backends/base/sql/alchemy/registry.py | 33 ++++++--- .../base/sql/registry/binary_infix.py | 67 +++++++++++-------- ibis/backends/base/sql/registry/main.py | 4 +- ibis/backends/clickhouse/registry.py | 4 +- ibis/backends/dask/execution/generic.py | 10 ++- ibis/backends/pandas/execution/generic.py | 8 ++- ibis/backends/tests/test_generic.py | 47 ++++++++++++- poetry.lock | 65 +++++++++++++++++- pyproject.toml | 1 + requirements.txt | 10 ++- 10 files changed, 197 insertions(+), 52 deletions(-) diff --git a/ibis/backends/base/sql/alchemy/registry.py b/ibis/backends/base/sql/alchemy/registry.py index 8ae5eb819488..6eed0b5b97a0 100644 --- a/ibis/backends/base/sql/alchemy/registry.py +++ b/ibis/backends/base/sql/alchemy/registry.py @@ -170,22 +170,35 @@ def _cast(t, expr): sa_arg = t.translate(arg) sa_type = t.get_sqla_type(target_type) - if isinstance(arg, ir.CategoryValue) and target_type == 'int32': + if isinstance(arg, ir.CategoryValue) and target_type == dt.int32: return sa_arg else: return sa.cast(sa_arg, sa_type) -def _contains(t, expr): - op = expr.op() - - left, right = (t.translate(arg) for arg in op.args) +def _contains(func): + def translate(t, expr): + op = expr.op() - return left.in_(right) + raw_left, raw_right = op.args + left = t.translate(raw_left) + right = t.translate(raw_right) + + if ( + # not a list expr + not isinstance(raw_right, ir.ListExpr) + # but still a column expr + and isinstance(raw_right, ir.ColumnExpr) + # wasn't already compiled into a select statement + and not isinstance(right, sa.sql.Selectable) + ): + right = sa.select(right) + else: + right = t.translate(raw_right) + return func(left, right) -def _not_contains(t, expr): - return sa.not_(_contains(t, expr)) + return translate def reduction(sa_func): @@ -462,8 +475,8 @@ def _string_join(t, expr): ops.Cast: _cast, ops.Coalesce: varargs(sa.func.coalesce), ops.NullIf: fixed_arity(sa.func.nullif, 2), - ops.Contains: _contains, - ops.NotContains: _not_contains, + ops.Contains: _contains(lambda left, right: left.in_(right)), + ops.NotContains: _contains(lambda left, right: left.notin_(right)), ops.Count: reduction(sa.func.count), ops.Sum: reduction(sa.func.sum), ops.Mean: reduction(sa.func.avg), diff --git a/ibis/backends/base/sql/registry/binary_infix.py b/ibis/backends/base/sql/registry/binary_infix.py index b6766ce0b9a5..cc31ca2da24d 100644 --- a/ibis/backends/base/sql/registry/binary_infix.py +++ b/ibis/backends/base/sql/registry/binary_infix.py @@ -1,3 +1,5 @@ +from typing import Literal + import ibis.expr.types as ir from ibis.backends.base.sql.registry import helpers @@ -53,34 +55,45 @@ def xor(translator, expr): return '({0} OR {1}) AND NOT ({0} AND {1})'.format(left_arg, right_arg) -def isin(translator, expr): - op = expr.op() - - left, right = op.args - if isinstance(right, ir.ListExpr) and not right: - return "FALSE" - - left_arg = translator.translate(left) - right_arg = translator.translate(right) - if helpers.needs_parens(left): - left_arg = helpers.parenthesize(left_arg) - - # we explicitly do NOT parenthesize the right side because it doesn't make - # sense to do so for ValueList operations - - return f"{left_arg} IN {right_arg}" - +def contains(op_string: Literal["IN", "NOT IN"]) -> str: + def translate(translator, expr): + from ibis.backends.base.sql.registry.main import table_array_view -def notin(translator, expr): - op = expr.op() + op = expr.op() - left, right = op.args - if isinstance(right, ir.ListExpr) and not right: - return "TRUE" + left, right = op.args + if isinstance(right, ir.ListExpr) and not right: + return {"NOT IN": "TRUE", "IN": "FALSE"}[op_string] - left_arg = translator.translate(left) - right_arg = translator.translate(right) - if helpers.needs_parens(left): - left_arg = helpers.parenthesize(left_arg) + left_arg = translator.translate(left) + if helpers.needs_parens(left): + left_arg = helpers.parenthesize(left_arg) - return f"{left_arg} NOT IN {right_arg}" + ctx = translator.context + + # special case non-foreign isin/notin expressions + if ( + not isinstance(right, ir.ListExpr) + and isinstance(right, ir.ColumnExpr) + # foreign refs are already been compiled correctly during + # TableColumn compilation + and not any( + ctx.is_foreign_expr(leaf.to_expr()) + for leaf in right.op().root_tables() + ) + ): + if not right.has_name(): + right = right.name("tmp") + right_arg = table_array_view( + translator, + right.to_projection().to_array(), + ) + else: + right_arg = translator.translate(right) + + # we explicitly do NOT parenthesize the right side because it doesn't + # make sense to do so for ValueList operations + + return f"{left_arg} {op_string} {right_arg}" + + return translate diff --git a/ibis/backends/base/sql/registry/main.py b/ibis/backends/base/sql/registry/main.py index b275aa41d06d..e6c54bdf17b1 100644 --- a/ibis/backends/base/sql/registry/main.py +++ b/ibis/backends/base/sql/registry/main.py @@ -345,8 +345,8 @@ def hash(translator, expr): ops.Least: varargs('least'), ops.Where: fixed_arity('if', 3), ops.Between: between, - ops.Contains: binary_infix.isin, - ops.NotContains: binary_infix.notin, + ops.Contains: binary_infix.contains("IN"), + ops.NotContains: binary_infix.contains("NOT IN"), ops.SimpleCase: case.simple_case, ops.SearchedCase: case.searched_case, ops.TableColumn: table_column, diff --git a/ibis/backends/clickhouse/registry.py b/ibis/backends/clickhouse/registry.py index 2473e774b0fc..a1b9aa9544d7 100644 --- a/ibis/backends/clickhouse/registry.py +++ b/ibis/backends/clickhouse/registry.py @@ -715,8 +715,8 @@ def _string_right(translator, expr): ops.DateAdd: binary_infix.binary_infix_op('+'), ops.DateSub: binary_infix.binary_infix_op('-'), ops.DateDiff: binary_infix.binary_infix_op('-'), - ops.Contains: binary_infix.isin, - ops.NotContains: binary_infix.notin, + ops.Contains: binary_infix.contains("IN"), + ops.NotContains: binary_infix.contains("NOT IN"), ops.TimestampAdd: binary_infix.binary_infix_op('+'), ops.TimestampSub: binary_infix.binary_infix_op('-'), ops.TimestampDiff: binary_infix.binary_infix_op('-'), diff --git a/ibis/backends/dask/execution/generic.py b/ibis/backends/dask/execution/generic.py index 1708e5a87226..107c1ccb24d9 100644 --- a/ibis/backends/dask/execution/generic.py +++ b/ibis/backends/dask/execution/generic.py @@ -125,13 +125,19 @@ ], ops.Contains: [ ( - (dd.Series, (collections.abc.Sequence, collections.abc.Set)), + ( + dd.Series, + (collections.abc.Sequence, collections.abc.Set, dd.Series), + ), execute_node_contains_series_sequence, ) ], ops.NotContains: [ ( - (dd.Series, (collections.abc.Sequence, collections.abc.Set)), + ( + dd.Series, + (collections.abc.Sequence, collections.abc.Set, dd.Series), + ), execute_node_not_contains_series_sequence, ) ], diff --git a/ibis/backends/pandas/execution/generic.py b/ibis/backends/pandas/execution/generic.py index 57244042ffa2..d3a74d075d71 100644 --- a/ibis/backends/pandas/execution/generic.py +++ b/ibis/backends/pandas/execution/generic.py @@ -870,14 +870,18 @@ def execute_node_string_join(op, args, **kwargs): @execute_node.register( - ops.Contains, pd.Series, (collections.abc.Sequence, collections.abc.Set) + ops.Contains, + pd.Series, + (collections.abc.Sequence, collections.abc.Set, pd.Series), ) def execute_node_contains_series_sequence(op, data, elements, **kwargs): return data.isin(elements) @execute_node.register( - ops.NotContains, pd.Series, (collections.abc.Sequence, collections.abc.Set) + ops.NotContains, + pd.Series, + (collections.abc.Sequence, collections.abc.Set, pd.Series), ) def execute_node_not_contains_series_sequence(op, data, elements, **kwargs): return ~(data.isin(elements)) diff --git a/ibis/backends/tests/test_generic.py b/ibis/backends/tests/test_generic.py index c4636618501a..0dc5028be38b 100644 --- a/ibis/backends/tests/test_generic.py +++ b/ibis/backends/tests/test_generic.py @@ -3,7 +3,6 @@ import numpy as np import pandas as pd -import pandas.testing as tm import pytest from packaging.version import parse as vparse from pytest import param @@ -453,8 +452,50 @@ def test_table_info(alltypes): ), ], ) -def test_isin_notin(alltypes, df, ibis_op, pandas_op): +def test_isin_notin(backend, alltypes, df, ibis_op, pandas_op): expr = alltypes[ibis_op] expected = df.loc[pandas_op(df)].sort_values(["id"]).reset_index(drop=True) result = expr.execute().sort_values(["id"]).reset_index(drop=True) - tm.assert_frame_equal(result, expected, check_index_type=False) + backend.assert_frame_equal(result, expected) + + +@pytest.mark.notyet( + ["dask"], + reason="dask doesn't support Series as isin/notin argument", + raises=NotImplementedError, +) +@pytest.mark.notimpl(["datafusion"]) +@pytest.mark.parametrize( + ("ibis_op", "pandas_op"), + [ + param( + _.string_col.isin(_.string_col), + lambda df: df.string_col.isin(df.string_col), + id="isin_col", + ), + param( + (_.bigint_col + 1).isin(_.string_col.cast("int64") + 1), + lambda df: (df.bigint_col + 1).isin( + df.string_col.astype("int64") + 1 + ), + id="isin_expr", + ), + param( + _.string_col.notin(_.string_col), + lambda df: ~df.string_col.isin(df.string_col), + id="notin_col", + ), + param( + (_.bigint_col + 1).notin(_.string_col.cast("int64") + 1), + lambda df: ~(df.bigint_col + 1).isin( + df.string_col.astype("int64") + 1 + ), + id="notin_expr", + ), + ], +) +def test_isin_notin_column_expr(backend, alltypes, df, ibis_op, pandas_op): + expr = alltypes[ibis_op].sort_by("id") + expected = df[pandas_op(df)].sort_values(["id"]).reset_index(drop=True) + result = expr.execute() + backend.assert_frame_equal(result, expected) diff --git a/poetry.lock b/poetry.lock index a274a9e09fe9..e1b7c952fa9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -311,6 +311,17 @@ termcolor = ">=1.1,<2.0" tomlkit = ">=0.5.3,<1.0.0" typing-extensions = ">=4.0.1,<5.0.0" +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + [[package]] name = "coverage" version = "6.3.3" @@ -1489,6 +1500,14 @@ dunamai = ">=1.12.0,<2.0.0" jinja2 = {version = ">=2.11.1,<4", markers = "python_version >= \"3.6\" and python_version < \"4.0\""} tomlkit = ">=0.4" +[[package]] +name = "pprintpp" +version = "0.4.0" +description = "A drop-in replacement for pprint that's actually pretty" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "prompt-toolkit" version = "3.0.29" @@ -1753,6 +1772,19 @@ aspect = ["aspectlib"] elasticsearch = ["elasticsearch"] histogram = ["pygal", "pygaljs"] +[[package]] +name = "pytest-clarity" +version = "1.0.1" +description = "A plugin providing an alternative, colourful diff output for failing assertions." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pprintpp = ">=0.4.0" +pytest = ">=3.5.0" +rich = ">=8.0.0" + [[package]] name = "pytest-cov" version = "3.0.0" @@ -1987,6 +2019,22 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +[[package]] +name = "rich" +version = "12.4.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "dev" +optional = false +python-versions = ">=3.6.3,<4.0.0" + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + [[package]] name = "shapely" version = "1.8.1.post1" @@ -2325,7 +2373,7 @@ visualization = ["graphviz"] [metadata] lock-version = "1.1" python-versions = ">=3.8,<3.11" -content-hash = "b0b8ee9cf6b827bcb766cae65b47ce9854c66fb3bb82fb2feb122a549c70cd00" +content-hash = "5ef0ba95f158a8fbdcb303d587d3421809a800f6ba40325b8063a636a370fbf3" [metadata.files] absolufy-imports = [ @@ -2755,6 +2803,10 @@ commitizen = [ {file = "commitizen-2.26.0-py3-none-any.whl", hash = "sha256:408fdc9527b3570425fb7e7a67d5e2e4269d7533d7ff91a88c63a382a8ae85c6"}, {file = "commitizen-2.26.0.tar.gz", hash = "sha256:a999051d5a3b2e9cce86d01ef2ad05232e827fa256cab89658dded46bcf94081"}, ] +commonmark = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] coverage = [ {file = "coverage-6.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df32ee0f4935a101e4b9a5f07b617d884a531ed5666671ff6ac66d2e8e8246d8"}, {file = "coverage-6.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75b5dbffc334e0beb4f6c503fb95e6d422770fd2d1b40a64898ea26d6c02742d"}, @@ -3376,6 +3428,10 @@ poetry-dynamic-versioning = [ {file = "poetry-dynamic-versioning-0.17.0.tar.gz", hash = "sha256:49ac538da63d0e719c61626358728295588177be3639aa323cc8001c45d0f7a6"}, {file = "poetry_dynamic_versioning-0.17.0-py3-none-any.whl", hash = "sha256:72a77365eb937930ff301fc89b7a6a4c39192fe52724c020991f775da1266830"}, ] +pprintpp = [ + {file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"}, + {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, +] prompt-toolkit = [ {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, @@ -3610,6 +3666,9 @@ pytest-benchmark = [ {file = "pytest-benchmark-3.4.1.tar.gz", hash = "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47"}, {file = "pytest_benchmark-3.4.1-py2.py3-none-any.whl", hash = "sha256:36d2b08c4882f6f997fd3126a3d6dfd70f3249cde178ed8bbc0b73db7c20f809"}, ] +pytest-clarity = [ + {file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"}, +] pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, @@ -3848,6 +3907,10 @@ requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] +rich = [ + {file = "rich-12.4.1-py3-none-any.whl", hash = "sha256:d13c6c90c42e24eb7ce660db397e8c398edd58acb7f92a2a88a95572b838aaa4"}, + {file = "rich-12.4.1.tar.gz", hash = "sha256:d239001c0fb7de985e21ec9a4bb542b5150350330bbc1849f835b9cbc8923b91"}, +] shapely = [ {file = "Shapely-1.8.1.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ca96a3314b7a38a3bb385531469de1fcf2b2c2979ec2aa4f37b4c70632cf1ad"}, {file = "Shapely-1.8.1.post1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:493902923fdd135316161a4ece5294ba3ce81accaa54540d2af3b93f7231143a"}, diff --git a/pyproject.toml b/pyproject.toml index 4af85831fac1..091d3981c0fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,7 @@ pydocstyle = ">=6.1.1,<7" pymdown-extensions = ">=9.1,<9.4" pytest = ">=7.0.0,<8" pytest-benchmark = ">=3.4.1,<4" +pytest-clarity = ">=1.0.1,<2" pytest-cov = ">=3.0.0,<4" pytest-mock = ">=3.6.1,<4" pytest-profiling = ">=1.7.0,<2" diff --git a/requirements.txt b/requirements.txt index 51d84f9f143b..0636b8e8ac0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ appnope==0.1.3; platform_system == "Darwin" and python_version >= "3.8" and sys_ argcomplete==1.12.3; python_full_version >= "3.6.2" and python_full_version < "4.0.0" asttokens==2.0.5; python_version >= "3.8" astunparse==1.6.3; python_version < "3.9" and python_version >= "3.7" and python_full_version >= "3.6.2" -atomicwrites==1.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") +atomicwrites==1.4.0; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7") or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.4.0" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7") atpublic==3.0.1; python_version >= "3.7" attrs==21.4.0; python_full_version >= "3.7.1" and python_version >= "3.7" and python_version < "4.0" babel==2.10.1; python_version >= "3.6" @@ -23,8 +23,9 @@ clickhouse-cityhash==1.0.2.3 clickhouse-driver==0.2.3; python_version >= "3.4" and python_version < "4" cligj==0.7.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.7" cloudpickle==2.0.0; python_version >= "3.8" -colorama==0.4.4; platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.6.2" and python_full_version < "4.0.0" and (python_version >= "3.8" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.8" and python_full_version >= "3.5.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") or sys_platform == "win32" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") and python_full_version >= "3.5.0") +colorama==0.4.4; platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.6.2" and python_full_version < "4.0.0" and (python_version >= "3.8" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.8" and python_full_version >= "3.5.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7") or sys_platform == "win32" and python_version >= "3.7" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7") and python_full_version >= "3.5.0") commitizen==2.26.0; python_full_version >= "3.6.2" and python_full_version < "4.0.0" +commonmark==0.9.1; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") coverage==6.3.3; python_version >= "3.7" dask==2022.5.0; python_version >= "3.8" datafusion==0.5.2; python_version >= "3.6" @@ -113,6 +114,7 @@ pickleshare==0.7.5; python_version >= "3.8" platformdirs==2.5.1; python_version >= "3.7" pluggy==1.0.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" poetry-dynamic-versioning==0.17.0; python_version >= "3.5" and python_version < "4.0" +pprintpp==0.4.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" prompt-toolkit==3.0.29; python_full_version >= "3.6.2" and python_version >= "3.8" and python_version < "4.0" and python_full_version < "4.0.0" psutil==5.9.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7" psycopg2==2.9.3; python_version >= "3.6" @@ -136,6 +138,7 @@ pyproj==3.3.1; python_version >= "3.8" pyrsistent==0.18.1; python_version >= "3.7" and python_version < "4.0" and python_full_version >= "3.7.1" pyspark==3.2.1; python_version >= "3.6" pytest-benchmark==3.4.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") +pytest-clarity==1.0.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") pytest-cov==3.0.0; python_version >= "3.6" pytest-forked==1.4.0; python_version >= "3.6" pytest-mock==3.7.0; python_version >= "3.7" @@ -156,6 +159,7 @@ pyzmq==22.3.0; python_version >= "3.7" questionary==1.10.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.6.2" and python_full_version < "4.0.0" regex==2022.4.24; python_version >= "3.6" requests==2.27.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") +rich==12.4.1; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") shapely==1.8.1.post1; python_version >= "3.6" six==1.16.0; python_full_version >= "3.7.1" and python_version >= "3.8" and python_version < "3.9" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7") and (python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8") smmap==5.0.0; python_version >= "3.7" @@ -176,7 +180,7 @@ tomlkit==0.10.2; python_version >= "3.6" and python_version < "4.0" and python_f toolz==0.11.2; python_version >= "3.5" tornado==6.1; python_version >= "3.7" traitlets==5.2.0; python_full_version >= "3.7.1" and python_version < "4" and python_version >= "3.8" -typing-extensions==4.2.0; python_version >= "3.7" and python_full_version >= "3.6.2" and python_version < "3.10" and python_full_version < "4.0.0" +typing-extensions==4.2.0; python_version >= "3.7" and python_full_version >= "3.6.3" and python_version < "3.9" and python_full_version < "4.0.0" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0") tzdata==2022.1; python_version >= "3.6" and python_version < "4" and platform_system == "Windows" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.6" and python_version < "4" and python_full_version >= "3.6.0") tzlocal==4.2; python_version >= "3.6" and python_version < "4" urllib3==1.26.9; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4"