Skip to content

Commit 7b4b583

Browse files
committed
tests: importorskip adbc which doesn't yet support free-threading, and linting cleanup
1 parent 7e0df85 commit 7b4b583

11 files changed

Lines changed: 87 additions & 165 deletions

tests/fast/adbc/test_adbc.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22
import sys
33
from pathlib import Path
44

5-
import adbc_driver_manager.dbapi
65
import numpy as np
76
import pyarrow
87
import pytest
98

10-
import adbc_driver_duckdb.dbapi
9+
import adbc_driver_duckdb
1110

12-
xfail = pytest.mark.xfail
1311
driver_path = adbc_driver_duckdb.driver_path()
1412

1513

14+
xfail = pytest.mark.xfail
15+
16+
1617
@pytest.fixture
1718
def duck_conn():
19+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
20+
1821
with adbc_driver_manager.dbapi.connect(driver=driver_path, entrypoint="duckdb_adbc_init") as conn:
1922
yield conn
2023

@@ -95,6 +98,8 @@ def test_connection_get_objects_filters(duck_conn):
9598

9699

97100
def test_commit(tmp_path):
101+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
102+
98103
db = Path(tmp_path) / "tmp.db"
99104
if db.exists():
100105
db.unlink()
@@ -142,6 +147,8 @@ def test_commit(tmp_path):
142147

143148

144149
def test_connection_get_table_schema(duck_conn):
150+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
151+
145152
with duck_conn.cursor() as cursor:
146153
# Test Default Schema
147154
cursor.execute("CREATE TABLE tableschema (ints BIGINT)")
@@ -209,6 +216,8 @@ def test_statement_query(duck_conn):
209216

210217
@xfail(sys.platform == "win32", reason="adbc-driver-manager returns an invalid table schema on windows")
211218
def test_insertion(duck_conn):
219+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
220+
212221
table = example_table()
213222
reader = table.to_reader()
214223

@@ -281,6 +290,8 @@ def test_read(duck_conn):
281290

282291

283292
def test_large_chunk(tmp_path):
293+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
294+
284295
num_chunks = 3
285296
chunk_size = 10_000
286297

@@ -318,6 +329,8 @@ def test_large_chunk(tmp_path):
318329

319330

320331
def test_dictionary_data(tmp_path):
332+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
333+
321334
data = ["apple", "banana", "apple", "orange", "banana", "banana"]
322335

323336
dict_type = pyarrow.dictionary(index_type=pyarrow.int32(), value_type=pyarrow.string())
@@ -346,6 +359,8 @@ def test_dictionary_data(tmp_path):
346359

347360

348361
def test_ree_data(tmp_path):
362+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
363+
349364
run_ends = pyarrow.array([3, 5, 6], type=pyarrow.int32()) # positions: [0-2], [3-4], [5]
350365
values = pyarrow.array(["apple", "banana", "orange"], type=pyarrow.string())
351366

tests/fast/adbc/test_statement_bind.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import sys
22

3-
import adbc_driver_manager
43
import pyarrow as pa
54
import pytest
65

@@ -10,6 +9,8 @@
109

1110

1211
def _import(handle):
12+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
13+
1314
"""Helper to import a C Data Interface handle."""
1415
if isinstance(handle, adbc_driver_manager.ArrowArrayStreamHandle):
1516
return pa.RecordBatchReader._import_from_c(handle.address)
@@ -20,6 +21,8 @@ def _import(handle):
2021

2122

2223
def _bind(stmt, batch) -> None:
24+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
25+
2326
array = adbc_driver_manager.ArrowArrayHandle()
2427
schema = adbc_driver_manager.ArrowSchemaHandle()
2528
batch._export_to_c(array.address, schema.address)
@@ -28,6 +31,8 @@ def _bind(stmt, batch) -> None:
2831

2932
class TestADBCStatementBind:
3033
def test_bind_multiple_rows(self):
34+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
35+
3136
data = pa.record_batch(
3237
[
3338
[1, 2, 3, 4],
@@ -141,6 +146,8 @@ def test_bind_composite_type(self):
141146
assert result == struct_array
142147

143148
def test_too_many_parameters(self):
149+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
150+
144151
data = pa.record_batch(
145152
[[12423], ["not a short string"]],
146153
names=["ints", "strings"],
@@ -170,6 +177,8 @@ def test_too_many_parameters(self):
170177

171178
@xfail(sys.platform == "win32", reason="adbc-driver-manager returns an invalid table schema on windows")
172179
def test_not_enough_parameters(self):
180+
adbc_driver_manager = pytest.importorskip("adbc_driver_manager")
181+
173182
data = pa.record_batch(
174183
[["not a short string"]],
175184
names=["strings"],

tests/fast/threading/test_basic_operations.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import gc
22
import random
33
import time
4+
import uuid
45
import weakref
56
from threading import get_ident
67

7-
import uuid
8-
9-
import pytest
10-
118
import duckdb
129

1310

@@ -23,16 +20,12 @@ def test_connection_instance_cache(tmp_path):
2320
thread_id = get_ident()
2421
for i in range(10):
2522
with duckdb.connect(tmp_path / f"{thread_id}_{uuid.uuid4()}.db") as conn:
26-
conn.execute(
27-
f"CREATE TABLE IF NOT EXISTS thread_{thread_id}_data_{i} (x BIGINT)"
28-
)
23+
conn.execute(f"CREATE TABLE IF NOT EXISTS thread_{thread_id}_data_{i} (x BIGINT)")
2924
conn.execute(f"INSERT INTO thread_{thread_id}_data_{i} VALUES (100), (100)")
3025

3126
time.sleep(random.uniform(0.0001, 0.001))
3227

33-
result = conn.execute(
34-
f"SELECT COUNT(*) FROM thread_{thread_id}_data_{i}"
35-
).fetchone()[0]
28+
result = conn.execute(f"SELECT COUNT(*) FROM thread_{thread_id}_data_{i}").fetchone()[0]
3629
assert result == 2, f"Iteration {i}: expected 2 rows, got {result}"
3730

3831

@@ -65,9 +58,7 @@ def test_cleanup():
6558
gc.collect()
6659

6760
alive_refs = [ref for ref in weak_refs if ref() is not None]
68-
assert len(alive_refs) <= 10, (
69-
f"{len(alive_refs)} connections still alive (expected <= 10)"
70-
)
61+
assert len(alive_refs) <= 10, f"{len(alive_refs)} connections still alive (expected <= 10)"
7162

7263

7364
def test_default_connection():
@@ -88,19 +79,15 @@ def test_type_system():
8879
duckdb.type("DOUBLE"),
8980
duckdb.type("BOOLEAN"),
9081
duckdb.list_type(duckdb.type("INTEGER")),
91-
duckdb.struct_type(
92-
{"a": duckdb.type("INTEGER"), "b": duckdb.type("VARCHAR")}
93-
),
82+
duckdb.struct_type({"a": duckdb.type("INTEGER"), "b": duckdb.type("VARCHAR")}),
9483
]
9584

9685
for t in types:
9786
assert t is not None, "type creation failed"
9887

9988
if i % 5 == 0:
10089
with duckdb.connect(":memory:") as conn:
101-
conn.execute(
102-
"CREATE TABLE test (a INTEGER, b VARCHAR, c DOUBLE, d BOOLEAN)"
103-
)
90+
conn.execute("CREATE TABLE test (a INTEGER, b VARCHAR, c DOUBLE, d BOOLEAN)")
10491
result = conn.execute("SELECT COUNT(*) FROM test").fetchone()
10592
assert result[0] == 0
10693

tests/fast/threading/test_concurrent_access.py

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
"""
2-
Concurrent access tests for DuckDB Python bindings with free threading support.
1+
"""Concurrent access tests for DuckDB Python bindings with free threading support.
32
43
These tests verify that the DuckDB Python module can handle concurrent access
54
from multiple threads safely, testing module state isolation, memory management,
65
and connection handling under various stress conditions.
76
"""
87

8+
import concurrent.futures
99
import gc
1010
import random
1111
import time
12-
import concurrent.futures
1312

1413
import pytest
1514

@@ -28,9 +27,7 @@ def test_shared_connection_stress(num_threads_testing):
2827
iterations = 10
2928

3029
with duckdb.connect(":memory:") as connection:
31-
connection.execute(
32-
"CREATE TABLE stress_test (id INTEGER, thread_id INTEGER, value TEXT)"
33-
)
30+
connection.execute("CREATE TABLE stress_test (id INTEGER, thread_id INTEGER, value TEXT)")
3431

3532
def worker_thread(thread_id: int) -> None:
3633
cursor = connection.cursor()
@@ -39,24 +36,16 @@ def worker_thread(thread_id: int) -> None:
3936
"INSERT INTO stress_test VALUES (?, ?, ?)",
4037
[i, thread_id, f"thread_{thread_id}_value_{i}"],
4138
)
42-
cursor.execute(
43-
"SELECT COUNT(*) FROM stress_test WHERE thread_id = ?", [thread_id]
44-
).fetchone()
39+
cursor.execute("SELECT COUNT(*) FROM stress_test WHERE thread_id = ?", [thread_id]).fetchone()
4540
time.sleep(random.uniform(0.0001, 0.001))
4641

47-
with concurrent.futures.ThreadPoolExecutor(
48-
max_workers=num_threads_testing
49-
) as executor:
50-
futures = [
51-
executor.submit(worker_thread, i) for i in range(num_threads_testing)
52-
]
42+
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads_testing) as executor:
43+
futures = [executor.submit(worker_thread, i) for i in range(num_threads_testing)]
5344
# Wait for all to complete, will raise if any fail
5445
for future in concurrent.futures.as_completed(futures):
5546
future.result()
5647

57-
total_rows = connection.execute("SELECT COUNT(*) FROM stress_test").fetchone()[
58-
0
59-
]
48+
total_rows = connection.execute("SELECT COUNT(*) FROM stress_test").fetchone()[0]
6049
expected_rows = num_threads_testing * iterations
6150
assert total_rows == expected_rows
6251

tests/fast/threading/test_connection_lifecycle_races.py

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
"""
2-
Test connection lifecycle races.
1+
"""Test connection lifecycle races.
32
43
Focused on DuckDBPyConnection constructor and Close
54
"""
65

7-
import gc
86
import concurrent.futures
7+
import gc
98

109
import pytest
1110

@@ -43,16 +42,9 @@ def attempt_close_connection(cursor, thread_id):
4342

4443
return True
4544

46-
with concurrent.futures.ThreadPoolExecutor(
47-
max_workers=num_threads_testing
48-
) as executor:
49-
futures = [
50-
executor.submit(attempt_close_connection, conn.cursor(), i)
51-
for i in range(num_threads_testing)
52-
]
53-
results = [
54-
future.result() for future in concurrent.futures.as_completed(futures)
55-
]
45+
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads_testing) as executor:
46+
futures = [executor.submit(attempt_close_connection, conn.cursor(), i) for i in range(num_threads_testing)]
47+
results = [future.result() for future in concurrent.futures.as_completed(futures)]
5648

5749
assert all(results)
5850

@@ -62,31 +54,20 @@ def test_cursor_operations_race(num_threads_testing):
6254
conn = duckdb.connect(":memory:")
6355
try:
6456
conn.execute("CREATE TABLE cursor_test (id INTEGER, name VARCHAR)")
65-
conn.execute(
66-
"INSERT INTO cursor_test SELECT i, 'name_' || i FROM range(100) t(i)"
67-
)
57+
conn.execute("INSERT INTO cursor_test SELECT i, 'name_' || i FROM range(100) t(i)")
6858

6959
def cursor_operations(thread_id):
7060
"""Perform cursor operations concurrently."""
7161
# Get a cursor
7262
cursor = conn.cursor()
73-
cursor.execute(
74-
f"SELECT * FROM cursor_test WHERE id % {num_threads_testing} = {thread_id}"
75-
)
76-
results = cursor.fetchall()
63+
cursor.execute(f"SELECT * FROM cursor_test WHERE id % {num_threads_testing} = {thread_id}")
64+
cursor.fetchall()
7765

7866
return True
7967

80-
with concurrent.futures.ThreadPoolExecutor(
81-
max_workers=num_threads_testing
82-
) as executor:
83-
futures = [
84-
executor.submit(cursor_operations, i)
85-
for i in range(num_threads_testing)
86-
]
87-
results = [
88-
future.result() for future in concurrent.futures.as_completed(futures)
89-
]
68+
with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads_testing) as executor:
69+
futures = [executor.submit(cursor_operations, i) for i in range(num_threads_testing)]
70+
results = [future.result() for future in concurrent.futures.as_completed(futures)]
9071

9172
assert all(results)
9273
finally:

tests/fast/threading/test_fetching.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
"""
2-
Test fetching operations.
3-
"""
1+
"""Test fetching operations."""
42

53
from threading import get_ident
64

7-
import pytest
8-
95
import duckdb
106

117

@@ -16,26 +12,18 @@ def test_fetching():
1612

1713
conn = duckdb.connect()
1814
try:
19-
batch_data = [
20-
(thread_id * 100 + i, f"name_{thread_id}_{i}") for i in range(iterations)
21-
]
15+
batch_data = [(thread_id * 100 + i, f"name_{thread_id}_{i}") for i in range(iterations)]
2216
conn.execute("CREATE TABLE batch_data (id BIGINT, name VARCHAR)")
2317
conn.executemany("INSERT INTO batch_data VALUES (?, ?)", batch_data)
2418

2519
# Test different fetch methods
26-
result1 = conn.execute(
27-
f"SELECT COUNT(*) FROM batch_data WHERE name LIKE 'name_{thread_id}_%'"
28-
).fetchone()
20+
result1 = conn.execute(f"SELECT COUNT(*) FROM batch_data WHERE name LIKE 'name_{thread_id}_%'").fetchone()
2921
assert result1[0] == iterations
3022

31-
result2 = conn.execute(
32-
f"SELECT COUNT(*) FROM batch_data WHERE name LIKE 'name_{thread_id}_%'"
33-
).fetchall()
23+
result2 = conn.execute(f"SELECT COUNT(*) FROM batch_data WHERE name LIKE 'name_{thread_id}_%'").fetchall()
3424
assert result2[0][0] == iterations
3525

36-
result3 = conn.execute(
37-
f"SELECT COUNT(*) FROM batch_data WHERE name LIKE 'name_{thread_id}_%'"
38-
).fetchdf()
26+
result3 = conn.execute(f"SELECT COUNT(*) FROM batch_data WHERE name LIKE 'name_{thread_id}_%'").fetchdf()
3927
assert len(result3) == 1
4028

4129
result4 = conn.execute(

0 commit comments

Comments
 (0)