From c6c0677c1fc9f0ccd06cd732b9c3556bf252cbe4 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Sat, 7 Sep 2024 09:27:07 -0400 Subject: [PATCH] refactor(backends): clean up memtable resources --- ibis/backends/__init__.py | 7 ++ ibis/backends/duckdb/__init__.py | 12 ++-- ibis/backends/exasol/__init__.py | 28 ++++---- ibis/backends/mssql/__init__.py | 19 +++--- ibis/backends/mysql/__init__.py | 17 ++--- ibis/backends/oracle/__init__.py | 24 +++---- ibis/backends/pandas/__init__.py | 3 + ibis/backends/polars/__init__.py | 10 ++- ibis/backends/postgres/__init__.py | 10 +-- ibis/backends/pyspark/__init__.py | 7 -- ibis/backends/risingwave/__init__.py | 16 ++--- ibis/backends/snowflake/__init__.py | 21 ++++-- ibis/backends/sql/__init__.py | 4 ++ ibis/backends/sqlite/__init__.py | 6 -- ibis/backends/tests/test_client.py | 52 +++++++++++++++ ibis/backends/trino/__init__.py | 5 -- poetry.lock | 96 ++++++++++++++-------------- pyproject.toml | 2 +- requirements-dev.txt | 2 +- 19 files changed, 195 insertions(+), 146 deletions(-) diff --git a/ibis/backends/__init__.py b/ibis/backends/__init__.py index 16775a2a1970b..28eaf1abde856 100644 --- a/ibis/backends/__init__.py +++ b/ibis/backends/__init__.py @@ -1116,6 +1116,7 @@ def _register_in_memory_tables(self, expr: ir.Expr) -> None: for memtable in expr.op().find(ops.InMemoryTable): if not self._table_exists(memtable.name): self._register_in_memory_table(memtable) + self._register_memtable_finalizer(memtable) def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: if self.supports_in_memory_tables: @@ -1123,6 +1124,12 @@ def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: f"{self.name} must implement `_register_in_memory_table` to support in-memory tables" ) + def _register_memtable_finalizer(self, op: ops.InMemoryTable) -> None: + if self.supports_in_memory_tables: + raise NotImplementedError( + f"{self.name} must implement `_register_memtable_finalizer` to support in-memory tables" + ) + def _run_pre_execute_hooks(self, expr: ir.Expr) -> None: """Backend-specific hooks to run before an expression is executed.""" self._register_udfs(expr) diff --git a/ibis/backends/duckdb/__init__.py b/ibis/backends/duckdb/__init__.py index ecd27619bee25..c786546b8372b 100644 --- a/ibis/backends/duckdb/__init__.py +++ b/ibis/backends/duckdb/__init__.py @@ -7,6 +7,7 @@ import os import urllib import warnings +import weakref from operator import itemgetter from pathlib import Path from typing import TYPE_CHECKING, Any @@ -158,12 +159,9 @@ def create_table( properties.append(sge.TemporaryProperty()) catalog = "temp" - temp_memtable_view = None - if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -242,9 +240,6 @@ def create_table( ).sql(self.name) ) - if temp_memtable_view is not None: - self.con.unregister(temp_memtable_view) - return self.table(name, database=(catalog, database)) def table( @@ -1601,6 +1596,11 @@ def _table_exists(self, name: str) -> bool: def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: self.con.register(op.name, op.data.to_pyarrow(op.schema)) + def _register_memtable_finalizer(self, op: ops.InMemoryTable): + # we can't use drop_table, because self.con.register creates a view, so + # use the corresponding unregister method + weakref.finalize(op, self.con.unregister, op.name) + def _register_udfs(self, expr: ir.Expr) -> None: con = self.con diff --git a/ibis/backends/exasol/__init__.py b/ibis/backends/exasol/__init__.py index ce4ccf0002b44..fce92c17227a2 100644 --- a/ibis/backends/exasol/__init__.py +++ b/ibis/backends/exasol/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations -import atexit import contextlib import datetime import re +import weakref from typing import TYPE_CHECKING, Any from urllib.parse import unquote_plus @@ -293,12 +293,14 @@ def process_item(item: Any): with self._safe_raw_sql(create_stmt_sql): if not df.empty: self.con.ext.insert_multi(name, rows) - atexit.register(self._clean_up_tmp_table, ident) - def _clean_up_tmp_table(self, ident: sge.Identifier) -> None: - with self._safe_raw_sql( - sge.Drop(kind="TABLE", this=ident, exists=True, cascade=True) - ): + def _register_memtable_finalizer(self, op: ops.InMemoryTable): + weakref.finalize(op, self._clean_up_tmp_table, op.name) + + def _clean_up_tmp_table(self, name: str) -> None: + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + sql = sge.Drop(kind="TABLE", this=ident, exists=True, cascade=True) + with self._safe_raw_sql(sql): pass def create_table( @@ -349,11 +351,9 @@ def create_table( quoted = self.compiler.quoted - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -382,8 +382,8 @@ def create_table( else: temp_name = name - table = sg.table(temp_name, catalog=database, quoted=quoted) - target = sge.Schema(this=table, expressions=column_defs) + table_expr = sg.table(temp_name, catalog=database, quoted=quoted) + target = sge.Schema(this=table_expr, expressions=column_defs) create_stmt = sge.Create(kind="TABLE", this=target) @@ -391,7 +391,7 @@ def create_table( with self._safe_raw_sql(create_stmt): if query is not None: self.con.execute( - sge.Insert(this=table, expression=query).sql(self.name) + sge.Insert(this=table_expr, expression=query).sql(self.name) ) if overwrite: @@ -399,14 +399,10 @@ def create_table( sge.Drop(kind="TABLE", this=this, exists=True).sql(self.name) ) self.con.execute( - f"RENAME TABLE {table.sql(self.name)} TO {this.sql(self.name)}" + f"RENAME TABLE {table_expr.sql(self.name)} TO {this.sql(self.name)}" ) if schema is None: - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) return self.table(name, database=database) # preserve the input schema if it was provided diff --git a/ibis/backends/mssql/__init__.py b/ibis/backends/mssql/__init__.py index cdf31c84bc5b1..18c8d97074f79 100644 --- a/ibis/backends/mssql/__init__.py +++ b/ibis/backends/mssql/__init__.py @@ -625,11 +625,9 @@ def create_table( properties.append(sge.TemporaryProperty()) catalog, db = None, None - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -657,11 +655,14 @@ def create_table( else: temp_name = name - table = sg.table( - "#" * temp + temp_name, catalog=catalog, db=db, quoted=self.compiler.quoted - ) + quoted = self.compiler.quoted raw_table = sg.table(temp_name, catalog=catalog, db=db, quoted=False) - target = sge.Schema(this=table, expressions=column_defs) + target = sge.Schema( + this=sg.table( + "#" * temp + temp_name, catalog=catalog, db=db, quoted=quoted + ), + expressions=column_defs, + ) create_stmt = sge.Create( kind="TABLE", @@ -669,7 +670,7 @@ def create_table( properties=sge.Properties(expressions=properties), ) - this = sg.table(name, catalog=catalog, db=db, quoted=self.compiler.quoted) + this = sg.table(name, catalog=catalog, db=db, quoted=quoted) raw_this = sg.table(name, catalog=catalog, db=db, quoted=False) with self._safe_ddl(create_stmt) as cur: if query is not None: @@ -702,10 +703,6 @@ def create_table( db = "dbo" if schema is None: - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) return self.table(name, database=(catalog, db)) # preserve the input schema if it was provided diff --git a/ibis/backends/mysql/__init__.py b/ibis/backends/mysql/__init__.py index d4a2f111c55cc..40c752b37d66d 100644 --- a/ibis/backends/mysql/__init__.py +++ b/ibis/backends/mysql/__init__.py @@ -403,11 +403,9 @@ def create_table( if temp: properties.append(sge.TemporaryProperty()) - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -435,8 +433,8 @@ def create_table( else: temp_name = name - table = sg.table(temp_name, catalog=database, quoted=self.compiler.quoted) - target = sge.Schema(this=table, expressions=column_defs) + table_expr = sg.table(temp_name, catalog=database, quoted=self.compiler.quoted) + target = sge.Schema(this=table_expr, expressions=column_defs) create_stmt = sge.Create( kind="TABLE", @@ -447,7 +445,9 @@ def create_table( this = sg.table(name, catalog=database, quoted=self.compiler.quoted) with self._safe_raw_sql(create_stmt) as cur: if query is not None: - insert_stmt = sge.Insert(this=table, expression=query).sql(self.name) + insert_stmt = sge.Insert(this=table_expr, expression=query).sql( + self.name + ) cur.execute(insert_stmt) if overwrite: @@ -455,15 +455,10 @@ def create_table( sge.Drop(kind="TABLE", this=this, exists=True).sql(self.name) ) cur.execute( - f"ALTER TABLE IF EXISTS {table.sql(self.name)} RENAME TO {this.sql(self.name)}" + f"ALTER TABLE IF EXISTS {table_expr.sql(self.name)} RENAME TO {this.sql(self.name)}" ) if schema is None: - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) - return self.table(name, database=database) # preserve the input schema if it was provided diff --git a/ibis/backends/oracle/__init__.py b/ibis/backends/oracle/__init__.py index 823d62f99c3db..d3180dbe56ae5 100644 --- a/ibis/backends/oracle/__init__.py +++ b/ibis/backends/oracle/__init__.py @@ -2,10 +2,10 @@ from __future__ import annotations -import atexit import contextlib import re import warnings +import weakref from functools import cached_property from operator import itemgetter from typing import TYPE_CHECKING, Any @@ -419,11 +419,9 @@ def create_table( if temp: properties.append(sge.TemporaryProperty()) - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -478,10 +476,6 @@ def create_table( ) if schema is None: - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) return self.table(name, database=database) # preserve the input schema if it was provided @@ -551,7 +545,8 @@ def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: insert_stmt, list(data.iloc[start:end].itertuples(index=False)) ) - atexit.register(self._clean_up_tmp_table, name) + def _register_memtable_finalizer(self, op: ops.InMemoryTable): + weakref.finalize(op, self._clean_up_tmp_table, op.name) def _get_schema_using_query(self, query: str) -> sch.Schema: name = util.gen_name("oracle_metadata") @@ -632,6 +627,13 @@ def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame: return OraclePandasData.convert_table(df, schema) def _clean_up_tmp_table(self, name: str) -> None: + dialect = self.dialect + + ident = sg.to_identifier(name, quoted=self.compiler.quoted) + + truncate = sge.TruncateTable(expressions=[ident]).sql(dialect) + drop = sge.Drop(kind="TABLE", this=ident).sql(dialect) + with self.begin() as bind: # global temporary tables cannot be dropped without first truncating them # @@ -640,9 +642,9 @@ def _clean_up_tmp_table(self, name: str) -> None: # ignore DatabaseError exceptions because the table may not exist # because it's already been deleted with contextlib.suppress(oracledb.DatabaseError): - bind.execute(f'TRUNCATE TABLE "{name}"') + bind.execute(truncate) with contextlib.suppress(oracledb.DatabaseError): - bind.execute(f'DROP TABLE "{name}"') + bind.execute(drop) - def _drop_cached_table(self, name): + def _drop_cached_table(self, name: str) -> None: self._clean_up_tmp_table(name) diff --git a/ibis/backends/pandas/__init__.py b/ibis/backends/pandas/__init__.py index b26a6e7ead9f7..3e12095f5d56d 100644 --- a/ibis/backends/pandas/__init__.py +++ b/ibis/backends/pandas/__init__.py @@ -331,6 +331,9 @@ def execute(self, query, params=None, limit="default", **kwargs): def _create_cached_table(self, name, expr): return self.create_table(name, expr.execute()) + def _register_memtable_finalizer(self, op: ops.InMemoryTable): + """No-op, let Python handle clean up.""" + @lazy_singledispatch def _convert_object(obj: Any, _conn): diff --git a/ibis/backends/polars/__init__.py b/ibis/backends/polars/__init__.py index 3de313cf37303..22b48de387db6 100644 --- a/ibis/backends/polars/__init__.py +++ b/ibis/backends/polars/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +import weakref from collections.abc import Iterable, Mapping from functools import lru_cache from pathlib import Path @@ -75,9 +76,14 @@ def table(self, name: str) -> ir.Table: schema = sch.infer(self._tables[name]) return ops.DatabaseTable(name, schema, self).to_expr() + def _table_exists(self, name: str) -> bool: + return name in self._tables + def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: - if (name := op.name) not in self._tables: - self._add_table(name, op.data.to_polars(op.schema).lazy()) + self._add_table(op.name, op.data.to_polars(op.schema).lazy()) + + def _register_memtable_finalizer(self, op: ops.InMemoryTable) -> None: + weakref.finalize(op, self.drop_table, op.name, force=True) @deprecated( as_of="9.1", diff --git a/ibis/backends/postgres/__init__.py b/ibis/backends/postgres/__init__.py index 797be26b94c72..da9c8ced548df 100644 --- a/ibis/backends/postgres/__init__.py +++ b/ibis/backends/postgres/__init__.py @@ -701,8 +701,8 @@ def create_table( else: temp_name = name - table = sg.table(temp_name, db=database, quoted=self.compiler.quoted) - target = sge.Schema(this=table, expressions=column_defs) + table_expr = sg.table(temp_name, db=database, quoted=self.compiler.quoted) + target = sge.Schema(this=table_expr, expressions=column_defs) create_stmt = sge.Create( kind="TABLE", @@ -713,7 +713,9 @@ def create_table( this = sg.table(name, catalog=database, quoted=self.compiler.quoted) with self._safe_raw_sql(create_stmt) as cur: if query is not None: - insert_stmt = sge.Insert(this=table, expression=query).sql(self.dialect) + insert_stmt = sge.Insert(this=table_expr, expression=query).sql( + self.dialect + ) cur.execute(insert_stmt) if overwrite: @@ -721,7 +723,7 @@ def create_table( sge.Drop(kind="TABLE", this=this, exists=True).sql(self.dialect) ) cur.execute( - f"ALTER TABLE IF EXISTS {table.sql(self.dialect)} RENAME TO {this.sql(self.dialect)}" + f"ALTER TABLE IF EXISTS {table_expr.sql(self.dialect)} RENAME TO {this.sql(self.dialect)}" ) if schema is None: diff --git a/ibis/backends/pyspark/__init__.py b/ibis/backends/pyspark/__init__.py index 0e0d81f8fca93..77ce1282b4773 100644 --- a/ibis/backends/pyspark/__init__.py +++ b/ibis/backends/pyspark/__init__.py @@ -597,13 +597,11 @@ def create_table( table_loc = self._to_sqlglot_table(database) catalog, db = self._to_catalog_db_tuple(table_loc) - temp_memtable_view = None if obj is not None: if isinstance(obj, ir.Expr): table = obj else: table = ibis.memtable(obj) - temp_memtable_view = table.op().name query = self.compile(table) mode = "overwrite" if overwrite else "error" with self._active_catalog_database(catalog, db): @@ -618,11 +616,6 @@ def create_table( else: raise com.IbisError("The schema or obj parameter is required") - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) - return self.table(name, database=(catalog, db)) def create_view( diff --git a/ibis/backends/risingwave/__init__.py b/ibis/backends/risingwave/__init__.py index 6ffc040c3d815..e75fd1f9296be 100644 --- a/ibis/backends/risingwave/__init__.py +++ b/ibis/backends/risingwave/__init__.py @@ -196,11 +196,9 @@ def create_table( f"Creating temp tables is not supported by {self.name}" ) - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -228,8 +226,8 @@ def create_table( else: temp_name = name - table = sg.table(temp_name, db=database, quoted=self.compiler.quoted) - target = sge.Schema(this=table, expressions=column_defs) + table_expr = sg.table(temp_name, db=database, quoted=self.compiler.quoted) + target = sge.Schema(this=table_expr, expressions=column_defs) if connector_properties is None: create_stmt = sge.Create( @@ -252,20 +250,18 @@ def create_table( this = sg.table(name, db=database, quoted=self.compiler.quoted) with self._safe_raw_sql(create_stmt) as cur: if query is not None: - insert_stmt = sge.Insert(this=table, expression=query).sql(self.dialect) + insert_stmt = sge.Insert(this=table_expr, expression=query).sql( + self.dialect + ) cur.execute(insert_stmt) if overwrite: self.drop_table(name, database=database, force=True) cur.execute( - f"ALTER TABLE {table.sql(self.dialect)} RENAME TO {this.sql(self.dialect)}" + f"ALTER TABLE {table_expr.sql(self.dialect)} RENAME TO {this.sql(self.dialect)}" ) if schema is None: - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) return self.table(name, database=database) # preserve the input schema if it was provided diff --git a/ibis/backends/snowflake/__init__.py b/ibis/backends/snowflake/__init__.py index 3dc38ed715cb2..bf14ac369fc26 100644 --- a/ibis/backends/snowflake/__init__.py +++ b/ibis/backends/snowflake/__init__.py @@ -8,6 +8,7 @@ import os import tempfile import warnings +import weakref from operator import itemgetter from pathlib import Path from typing import TYPE_CHECKING, Any @@ -677,6 +678,19 @@ def _register_in_memory_table(self, op: ops.InMemoryTable) -> None: pq.write_table(data, path, compression="zstd") self.read_parquet(path, table_name=name) + def _register_memtable_finalizer(self, op: ops.InMemoryTable): + def drop_table(con, sql: str): + if not con.is_closed(): + with con.cursor() as cur: + cur.execute(sql) + + drop_stmt = sg.exp.Drop( + kind="TABLE", + this=sg.table(op.name, quoted=self.compiler.quoted), + exists=True, + ) + weakref.finalize(op, drop_table, self.con, drop_stmt.sql(self.dialect)) + def create_catalog(self, name: str, force: bool = False) -> None: current_catalog = self.current_catalog current_database = self.current_database @@ -847,11 +861,9 @@ def create_table( if comment is not None: properties.append(sge.SchemaCommentProperty(this=sge.convert(comment))) - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): table = ibis.memtable(obj) - temp_memtable_view = table.op().name else: table = obj @@ -872,11 +884,6 @@ def create_table( with self._safe_raw_sql(create_stmt): pass - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) - return self.table(name, database=(catalog, db)) def read_csv( diff --git a/ibis/backends/sql/__init__.py b/ibis/backends/sql/__init__.py index 1b7896fb794d7..a5e43c2dfd081 100644 --- a/ibis/backends/sql/__init__.py +++ b/ibis/backends/sql/__init__.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import weakref from functools import partial from typing import TYPE_CHECKING, Any, ClassVar @@ -618,3 +619,6 @@ def _register_pandas_udf(self, udf_node: ops.ScalarUDF) -> str: raise NotImplementedError( f"pandas UDFs are not supported in the {self.dialect} backend" ) + + def _register_memtable_finalizer(self, op: ops.InMemoryTable): + weakref.finalize(op, self.drop_table, op.name, force=True) diff --git a/ibis/backends/sqlite/__init__.py b/ibis/backends/sqlite/__init__.py index af0f0b29671b1..0299a1926abe4 100644 --- a/ibis/backends/sqlite/__init__.py +++ b/ibis/backends/sqlite/__init__.py @@ -480,11 +480,9 @@ def create_table( if schema is not None: schema = ibis.schema(schema) - temp_memtable_view = None if obj is not None: if not isinstance(obj, ir.Expr): obj = ibis.memtable(obj) - temp_memtable_view = obj.op().name self._run_pre_execute_hooks(obj) @@ -540,10 +538,6 @@ def create_table( ) if schema is None: - # Clean up temporary memtable if we've created one - # for in-memory reads - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) return self.table(name, database=database) # preserve the input schema if it was provided diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index bfb97a5c046ab..c9cd4ac14382f 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -1770,3 +1770,55 @@ def test_insert_into_table_missing_columns(con, temp_table): expected_result = {"a": [1], "b": [1]} assert result == expected_result + + +@pytest.mark.never( + ["pandas", "dask"], raises=AssertionError, reason="backend is going away" +) +@pytest.mark.notyet(["druid"], raises=AssertionError, reason="can't drop tables") +@pytest.mark.notyet( + ["clickhouse"], raises=AssertionError, reason="memtables are assembled every time" +) +def test_memtable_cleanup(con): + t = ibis.memtable({"a": [1, 2, 3], "b": list("def")}, name="temp_memtable") + + # the table isn't registered until we actually execute, and since we + # haven't yet executed anything, the table shouldn't be there + assert "temp_memtable" not in con.list_tables() + + # execute, which means the table is registered and should be visible in + # con.list_tables() + con.execute(t.select("a")) + assert "temp_memtable" in con.list_tables() + + con.execute(t.select("b")) + assert "temp_memtable" in con.list_tables() + + # remove all references to `t`, which means the `op` shouldn't be reachable + # and the table should thus be dropped and no longer visible in + # con.list_tables() + del t + assert "temp_memtable" not in con.list_tables() + + +@pytest.mark.never( + ["pandas", "dask"], raises=AssertionError, reason="backend is going away" +) +@pytest.mark.notyet(["druid"], raises=AssertionError, reason="can't drop tables") +@pytest.mark.notyet( + ["clickhouse"], raises=AssertionError, reason="memtables are assembled every time" +) +def test_memtable_cleanup_by_overwriting_variable(con): + t = ibis.memtable({"a": [1, 2, 3], "b": list("def")}, name="temp_memtable") + + assert "temp_memtable" not in con.list_tables() + + con.execute(t.select("a")) + assert "temp_memtable" in con.list_tables() + + con.execute(t.select("b")) + assert "temp_memtable" in con.list_tables() + + # original `t` is gone, so this should drop the table + t = None + assert "temp_memtable" not in con.list_tables() diff --git a/ibis/backends/trino/__init__.py b/ibis/backends/trino/__init__.py index c8e1d7b932ded..44cf2c2ec14b8 100644 --- a/ibis/backends/trino/__init__.py +++ b/ibis/backends/trino/__init__.py @@ -492,13 +492,11 @@ def create_table( if comment: property_list.append(sge.SchemaCommentProperty(this=sge.convert(comment))) - temp_memtable_view = None if obj is not None: if isinstance(obj, ir.Table): table = obj else: table = ibis.memtable(obj, schema=schema) - temp_memtable_view = table.op().name self._run_pre_execute_hooks(table) @@ -542,9 +540,6 @@ def create_table( ).sql(self.name) ) - if temp_memtable_view is not None: - self.drop_table(temp_memtable_view) - return self.table(orig_table_ref.name, database=(catalog, db)) def _fetch_from_cursor(self, cursor, schema: sch.Schema) -> pd.DataFrame: diff --git a/poetry.lock b/poetry.lock index 785f7f77f7e5e..0a22a9383238f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1512,57 +1512,57 @@ toml = ["tomli"] [[package]] name = "duckdb" -version = "1.0.0" +version = "1.0.1.dev5313" description = "DuckDB in-process database" optional = true python-versions = ">=3.7.0" files = [ - {file = "duckdb-1.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:4a8ce2d1f9e1c23b9bab3ae4ca7997e9822e21563ff8f646992663f66d050211"}, - {file = "duckdb-1.0.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:19797670f20f430196e48d25d082a264b66150c264c1e8eae8e22c64c2c5f3f5"}, - {file = "duckdb-1.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b71c342090fe117b35d866a91ad6bffce61cd6ff3e0cff4003f93fc1506da0d8"}, - {file = "duckdb-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25dd69f44ad212c35ae2ea736b0e643ea2b70f204b8dff483af1491b0e2a4cec"}, - {file = "duckdb-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8da5f293ecb4f99daa9a9352c5fd1312a6ab02b464653a0c3a25ab7065c45d4d"}, - {file = "duckdb-1.0.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3207936da9967ddbb60644ec291eb934d5819b08169bc35d08b2dedbe7068c60"}, - {file = "duckdb-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1128d6c9c33e883b1f5df6b57c1eb46b7ab1baf2650912d77ee769aaa05111f9"}, - {file = "duckdb-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:02310d263474d0ac238646677feff47190ffb82544c018b2ff732a4cb462c6ef"}, - {file = "duckdb-1.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:75586791ab2702719c284157b65ecefe12d0cca9041da474391896ddd9aa71a4"}, - {file = "duckdb-1.0.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:83bb415fc7994e641344f3489e40430ce083b78963cb1057bf714ac3a58da3ba"}, - {file = "duckdb-1.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:bee2e0b415074e84c5a2cefd91f6b5ebeb4283e7196ba4ef65175a7cef298b57"}, - {file = "duckdb-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa5a4110d2a499312609544ad0be61e85a5cdad90e5b6d75ad16b300bf075b90"}, - {file = "duckdb-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa389e6a382d4707b5f3d1bc2087895925ebb92b77e9fe3bfb23c9b98372fdc"}, - {file = "duckdb-1.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ede6f5277dd851f1a4586b0c78dc93f6c26da45e12b23ee0e88c76519cbdbe0"}, - {file = "duckdb-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b88cdbc0d5c3e3d7545a341784dc6cafd90fc035f17b2f04bf1e870c68456e5"}, - {file = "duckdb-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd1693cdd15375156f7fff4745debc14e5c54928589f67b87fb8eace9880c370"}, - {file = "duckdb-1.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c65a7fe8a8ce21b985356ee3ec0c3d3b3b2234e288e64b4cfb03356dbe6e5583"}, - {file = "duckdb-1.0.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:e5a8eda554379b3a43b07bad00968acc14dd3e518c9fbe8f128b484cf95e3d16"}, - {file = "duckdb-1.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:a1b6acdd54c4a7b43bd7cb584975a1b2ff88ea1a31607a2b734b17960e7d3088"}, - {file = "duckdb-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a677bb1b6a8e7cab4a19874249d8144296e6e39dae38fce66a80f26d15e670df"}, - {file = "duckdb-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:752e9d412b0a2871bf615a2ede54be494c6dc289d076974eefbf3af28129c759"}, - {file = "duckdb-1.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3aadb99d098c5e32d00dc09421bc63a47134a6a0de9d7cd6abf21780b678663c"}, - {file = "duckdb-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83b7091d4da3e9301c4f9378833f5ffe934fb1ad2b387b439ee067b2c10c8bb0"}, - {file = "duckdb-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:6a8058d0148b544694cb5ea331db44f6c2a00a7b03776cc4dd1470735c3d5ff7"}, - {file = "duckdb-1.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e40cb20e5ee19d44bc66ec99969af791702a049079dc5f248c33b1c56af055f4"}, - {file = "duckdb-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7bce1bc0de9af9f47328e24e6e7e39da30093179b1c031897c042dd94a59c8e"}, - {file = "duckdb-1.0.0-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8355507f7a04bc0a3666958f4414a58e06141d603e91c0fa5a7c50e49867fb6d"}, - {file = "duckdb-1.0.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:39f1a46f5a45ad2886dc9b02ce5b484f437f90de66c327f86606d9ba4479d475"}, - {file = "duckdb-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d29ba477b27ae41676b62c8fae8d04ee7cbe458127a44f6049888231ca58fa"}, - {file = "duckdb-1.0.0-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:1bea713c1925918714328da76e79a1f7651b2b503511498ccf5e007a7e67d49e"}, - {file = "duckdb-1.0.0-cp38-cp38-macosx_12_0_universal2.whl", hash = "sha256:bfe67f3bcf181edbf6f918b8c963eb060e6aa26697d86590da4edc5707205450"}, - {file = "duckdb-1.0.0-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:dbc6093a75242f002be1d96a6ace3fdf1d002c813e67baff52112e899de9292f"}, - {file = "duckdb-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba1881a2b11c507cee18f8fd9ef10100be066fddaa2c20fba1f9a664245cd6d8"}, - {file = "duckdb-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:445d0bb35087c522705c724a75f9f1c13f1eb017305b694d2686218d653c8142"}, - {file = "duckdb-1.0.0-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:224553432e84432ffb9684f33206572477049b371ce68cc313a01e214f2fbdda"}, - {file = "duckdb-1.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d3914032e47c4e76636ad986d466b63fdea65e37be8a6dfc484ed3f462c4fde4"}, - {file = "duckdb-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:af9128a2eb7e1bb50cd2c2020d825fb2946fdad0a2558920cd5411d998999334"}, - {file = "duckdb-1.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:dd2659a5dbc0df0de68f617a605bf12fe4da85ba24f67c08730984a0892087e8"}, - {file = "duckdb-1.0.0-cp39-cp39-macosx_12_0_universal2.whl", hash = "sha256:ac5a4afb0bc20725e734e0b2c17e99a274de4801aff0d4e765d276b99dad6d90"}, - {file = "duckdb-1.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:2c5a53bee3668d6e84c0536164589d5127b23d298e4c443d83f55e4150fafe61"}, - {file = "duckdb-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b980713244d7708b25ee0a73de0c65f0e5521c47a0e907f5e1b933d79d972ef6"}, - {file = "duckdb-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cbd4f9fe7b7a56eff96c3f4d6778770dd370469ca2212eddbae5dd63749db5"}, - {file = "duckdb-1.0.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed228167c5d49888c5ef36f6f9cbf65011c2daf9dcb53ea8aa7a041ce567b3e4"}, - {file = "duckdb-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46d8395fbcea7231fd5032a250b673cc99352fef349b718a23dea2c0dd2b8dec"}, - {file = "duckdb-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6ad1fc1a4d57e7616944166a5f9417bdbca1ea65c490797e3786e3a42e162d8a"}, - {file = "duckdb-1.0.0.tar.gz", hash = "sha256:a2a059b77bc7d5b76ae9d88e267372deff19c291048d59450c431e166233d453"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:220445c06db93b2eef09ba63e752957d24248e63898bd7901f3430363170d0e7"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:044c517d205e578a0e69cba17c98f7c149f433a3a6b000b71b0a425e1f9709c3"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:819a4a92a29d54ca13407162b6b36d38f96d2c3a27e7945498fa2dfcaea9babf"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65be42b0579e5ba7908d401ea7699b2a525e1a972152fc6e160f8be6d95860cf"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60de39594f8244b6ce29b0568633049d08d877286704921f64da38371b60e954"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0f52f2039e35e91f4157427337177e542338751ea776e2092b984cbb6c1df8b"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1643c220860ddc6970b8fbf96a7bf787fbdcb220fcc329430b99412e64e27783"}, + {file = "duckdb-1.0.1.dev5313-cp310-cp310-win_amd64.whl", hash = "sha256:0175c08c9df2422808af28f9e60b9b14c22831e2055774ed49b451f1b00778b2"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:983b5bf7c650054cee93e0d9514ccb783b2ad0e89573cd82341678a936d70453"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:cd9015f69fe7dc361afe657d2f8e3bb3907d4d0c5830995da88e338a25b2248c"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:7649cd5f5a777c3522fb74cc8bd5f2fa9449be292372740b30fb3927c6dc5823"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63ca32958a9ab7167fe00c21eff0ce8d3886bc4d35e9e6d2a139f5044bdd74b1"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3963a6f2b621c8eeba412cf36651be61493fb07e42a8e1e668c54d779a88956"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a67e1d955bf68e32e4d8e1ae7475ceba6737ba25bf4d05d609962f15b012071"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:405a581a05d9f74d5b5ae0e0260521e152f895ad6654dd5039112295808dcdc8"}, + {file = "duckdb-1.0.1.dev5313-cp311-cp311-win_amd64.whl", hash = "sha256:cb7ab5822d50df27e58852192cf42e9c230a050a5ebf93c9596a9cf226371bd3"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:8182b6d32a4d553765bfd8e0f0a78c8c23fc83c9c0e2a7f243f7dd5d886b7b79"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:c8bec8571030337ddca525b00424158a0a36299d0a0fb9385066d04b7bc18ba9"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:a7c65beaeecced26cb4bc54db3918c3718828a34af84622400b1fd7c92b7a1a8"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae4b409f97e44626774d741e1bc18490fafa88127bf45a1f65e4afa6d8a17b12"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e710ac49bd5051c22cbdbeb4fec9ec3d8d7d41a2abb58eb9ddc3dad52099cb67"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40420b1ba4e2a990479be6e1ad57eac798fe79349b8785b803221673ec560ee"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:071c7a13691c67992a56b13c797fb5264b2a6ba599d2ccca163f2aee4bc1ca6e"}, + {file = "duckdb-1.0.1.dev5313-cp312-cp312-win_amd64.whl", hash = "sha256:52056d9bb377a051bc2a5402c12075d8d53f6fee8c507925557da3882997bb07"}, + {file = "duckdb-1.0.1.dev5313-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3325904f8d76b8b7d76043f9ee9578d68635fc34782fd13ed61a18e99f4f98f"}, + {file = "duckdb-1.0.1.dev5313-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:653d985d891261b78848dfd898e293df2418ee98d57c18840bcf2dd87a41941d"}, + {file = "duckdb-1.0.1.dev5313-cp37-cp37m-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bc2c203f86345621dc059864cc5570151e0cfca31127423521ee7e375446160"}, + {file = "duckdb-1.0.1.dev5313-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:70b5666e48a7f767a702fd05ce52531e817e1b4d1f95b6b0ddc2b2660c143ab4"}, + {file = "duckdb-1.0.1.dev5313-cp37-cp37m-win_amd64.whl", hash = "sha256:fea8046a14841233cdc99fe59037a07e9790576f63d52c368e042c6e73cf7635"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:3d19ed5c02ca2875b7430bec48c7de2bfaf40c25b6d1f5938f874bdd0c40d065"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-macosx_12_0_universal2.whl", hash = "sha256:514ffa9d9b0b735da113e1d2367979345597355325897ee2561fc8dcf656dbf3"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:13cbe808c5a8038a9892bd96dca2b9caa11f53192288fcb572ef3040a68d5cf6"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a34d1268125ceb2ef7db315a2b62862893fa2446efc7f3d8d0783c4f0fb0c7af"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e52a9957999329765adbbda46696011db201a8607b14d29579ff3293596cd74"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f3cf67cb8118e51cd4e69eea9d37a9aa6dcdeb4f09bb1be69d6679344177d88"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15d6cc1ba741f71f5516bb71517a3ac2d0c118c29b07e0bd7e3b5b2cca8cc370"}, + {file = "duckdb-1.0.1.dev5313-cp38-cp38-win_amd64.whl", hash = "sha256:2d47eaabc8d5c1cadc56db1607d69262c759dfea5f0093591619aa7b6a7574f7"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:ee666eab18df49b41c3f2bdce75c86d0d1386530c53106fceae2cdf2306b8181"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-macosx_12_0_universal2.whl", hash = "sha256:960507c63889afd30bf816aaf807f1cfd1bc28c14fed53bb1de54f4804102599"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7a446307964c4913adc74e5fa92484c4ef93b31067c9d35e001530d1d5f2581e"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ee69b6c18719a0b6977648fef25db23d2c3c1de26993685b76adc80c734a43"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61e4d4f237efa2524be1eef2e1cb2bde4ad05f49aae7d5e899f34fcef90cd9d9"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7302d73830e05b72b6466e77437d946badb86fa68cf9270d0949ca64eefcd6dc"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19b7257ace7f74a8b4b5ea8bfb6d81fccd4ffa27500395e5c2734b15eee1f4e0"}, + {file = "duckdb-1.0.1.dev5313-cp39-cp39-win_amd64.whl", hash = "sha256:8f346419d9d924125958246680797c0605c625cb32f2337bee6c30102d8ecfb1"}, + {file = "duckdb-1.0.1.dev5313.tar.gz", hash = "sha256:4d02cf57ae892cf753de53b99e1683d92f87c12354cd43bb5333e95e7273f24a"}, ] [[package]] @@ -7941,4 +7941,4 @@ visualization = ["graphviz"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "eef90ccc5c638e17fff405ae65db499f53dda72ff68ecfd08c17a63621c7e268" +content-hash = "14f078326ac17a8366ad8078248412cae92262d229827ef7dbddbcfa3e2eabc0" diff --git a/pyproject.toml b/pyproject.toml index cc94bc90d06e0..a1cfc96650fa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,7 +62,7 @@ dask = { version = ">=2022.9.1,<2024.3.0", optional = true, extras = [ datafusion = { version = ">=0.6,<41", optional = true } db-dtypes = { version = ">=0.3,<2", optional = true } deltalake = { version = ">=0.9.0,<1", optional = true } -duckdb = { version = ">=0.8.1,<2", optional = true } +duckdb = { version = ">=0.8.1,<2", optional = true, allow-prereleases = true } geopandas = { version = ">=0.6,<2", optional = true } geoarrow-types = { version = ">=0.2,<1", optional = true } pyproj = { version = ">=3.3.0,<4", optional = true } diff --git a/requirements-dev.txt b/requirements-dev.txt index ad517f4f58a2a..607b00787bcde 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -51,7 +51,7 @@ defusedxml==0.7.1 ; python_version >= "3.10" and python_version < "3.13" deltalake==0.19.1 ; python_version >= "3.10" and python_version < "4.0" distlib==0.3.8 ; python_version >= "3.10" and python_version < "4.0" doit==0.36.0 ; python_version >= "3.10" and python_version < "3.13" -duckdb==1.0.0 ; python_version >= "3.10" and python_version < "4.0" +duckdb==1.0.1.dev5313 ; python_version >= "3.10" and python_version < "4.0" dulwich==0.21.7 ; python_version >= "3.10" and python_version < "4.0" dunamai==1.22.0 ; python_version >= "3.10" and python_version < "4.0" exceptiongroup==1.2.2 ; python_version >= "3.10" and python_version < "3.11"